This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
commit 7245fbd82735eed3fc5510f8b468b77f1e984fb2 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sun Nov 3 11:30:18 2024 -0500 Add ValidatingObjectInputStream.ObjectStreamClassPredicate to allow configuration reuse --- src/changes/changes.xml | 1 + .../serialization/ValidatingObjectInputStream.java | 432 +++++++++++++++++++-- src/site/xdoc/description.xml | 14 +- .../io/serialization/MoreComplexObjectTest.java | 18 +- .../ValidatingObjectInputStreamTest.java | 188 +++++++-- 5 files changed, 578 insertions(+), 75 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index edfc12892..75de60bac 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -61,6 +61,7 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="add" due-to="Gary Gregory">Add RandomAccessFileMode.io(String).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add FileAlterationObserver.Builder() and deprecate most constructors.</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add IOUtils.readLines(CharSequence).</action> + <action dev="ggregory" type="add" due-to="Gary Gregory">Add ValidatingObjectInputStream.ObjectStreamClassPredicate to allow configuration reuse.</action> <!-- UPDATE --> <action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 74 to 78 #670, #676, #679, #688.</action> <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.15.1 to 1.15.8 #672, #673, #685, #686, #694, #696.</action> diff --git a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java index 06b6841d9..6d701c91b 100644 --- a/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java +++ b/src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java @@ -25,6 +25,7 @@ import java.io.ObjectInputStream; import java.io.ObjectStreamClass; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -42,10 +43,10 @@ import org.apache.commons.io.build.AbstractStreamBuilder; * </p> * * <pre>{@code - * // Data + * // Defining Object fixture * final HashMap<String, Integer> map1 = new HashMap<>(); * map1.put("1", 1); - * // Write + * // Writing serialized fixture * final byte[] byteArray; * try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); * final ObjectOutputStream oos = new ObjectOutputStream(baos)) { @@ -53,11 +54,25 @@ import org.apache.commons.io.build.AbstractStreamBuilder; * oos.flush(); * byteArray = baos.toByteArray(); * } - * // Read + * // Reading * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); - * ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setInputStream(bais).get()) { + * ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + * .accept(HashMap.class, Number.class, Integer.class) + * .setInputStream(bais) + * .get()) { + * // String.class is automatically accepted + * final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); + * assertEquals(map1, map2); + * } + * // Reusing a configuration + * final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate() + * .accept(HashMap.class, Number.class, Integer.class); + * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + * ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + * .setPredicate(predicate) + * .setInputStream(bais) + * .get()) { * // String.class is automatically accepted - * vois.accept(HashMap.class, Number.class, Integer.class); * final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); * assertEquals(map1, map2); * } @@ -93,9 +108,318 @@ public class ValidatingObjectInputStream extends ObjectInputStream { // @formatter:on public static class Builder extends AbstractStreamBuilder<ValidatingObjectInputStream, Builder> { + private ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate(); + + /** + * Constructs a new instance. + * + * @deprecated Use {@link #builder()}. + */ + @Deprecated + public Builder() { + // empty + } + + /** + * Accepts the specified classes for deserialization, unless they are otherwise rejected. + * + * @param classes Classes to accept + * @return this object + * @since 2.18.0 + */ + public Builder accept(final Class<?>... classes) { + predicate.accept(classes); + return this; + } + + /** + * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected. + * + * @param matcher a class name matcher to <em>accept</em> objects. + * @return this instance. + * @since 2.18.0 + */ + public Builder accept(final ClassNameMatcher matcher) { + predicate.accept(matcher); + return this; + } + + /** + * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected. + * + * @param pattern a Pattern for compiled regular expression. + * @return this instance. + * @since 2.18.0 + */ + public Builder accept(final Pattern pattern) { + predicate.accept(pattern); + return this; + } + + /** + * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected. + * + * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) + * FilenameUtils.wildcardMatch} + * @return this instance. + * @since 2.18.0 + */ + public Builder accept(final String... patterns) { + predicate.accept(patterns); + return this; + } + @Override public ValidatingObjectInputStream get() throws IOException { - return new ValidatingObjectInputStream(getInputStream()); + return new ValidatingObjectInputStream(getInputStream(), predicate); + } + + /** + * Gets the predicate. + * + * @return the predicate. + * @since 2.18.0 + */ + public ObjectStreamClassPredicate getPredicate() { + return predicate; + } + + /** + * Rejects the specified classes for deserialization, even if they are otherwise accepted. + * + * @param classes Classes to reject + * @return this instance. + * @since 2.18.0 + */ + public Builder reject(final Class<?>... classes) { + predicate.reject(classes); + return this; + } + + /** + * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted. + * + * @param matcher the matcher to use + * @return this instance. + * @since 2.18.0 + */ + public Builder reject(final ClassNameMatcher matcher) { + predicate.reject(matcher); + return this; + } + + /** + * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted. + * + * @param pattern standard Java regexp + * @return this instance. + * @since 2.18.0 + */ + public Builder reject(final Pattern pattern) { + predicate.reject(pattern); + return this; + } + + /** + * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted. + * + * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) + * FilenameUtils.wildcardMatch} + * @return this instance. + * @since 2.18.0 + */ + public Builder reject(final String... patterns) { + predicate.reject(patterns); + return this; + } + + /** + * Sets the predicate, null resets to an empty new ObjectStreamClassPredicate. + * + * @param predicate the predicate. + * @return this instance. + * @since 2.18.0 + */ + public Builder setPredicate(final ObjectStreamClassPredicate predicate) { + this.predicate = predicate != null ? predicate : new ObjectStreamClassPredicate(); + return this; + } + + } + + /** + * A predicate (boolean-valued function) of one argument to accept and reject classes. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @since 2.18.0 + */ + public static class ObjectStreamClassPredicate implements Predicate<ObjectStreamClass> { + + // This is not a Set for now to avoid ClassNameMatchers requiring proper implementations of hashCode() and equals(). + private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>(); + + // This is not a Set for now to avoid ClassNameMatchers requiring proper implementations of hashCode() and equals(). + private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>(); + + /** + * Constructs a new instance. + */ + public ObjectStreamClassPredicate() { + // empty + } + + /** + * Accepts the specified classes for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param classes Classes to accept + * @return this object + */ + public ObjectStreamClassPredicate accept(final Class<?>... classes) { + Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add); + return this; + } + + /** + * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param matcher a class name matcher to <em>accept</em> objects. + * @return this instance. + */ + public ObjectStreamClassPredicate accept(final ClassNameMatcher matcher) { + acceptMatchers.add(matcher); + return this; + } + + /** + * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param pattern a Pattern for compiled regular expression. + * @return this instance. + */ + public ObjectStreamClassPredicate accept(final Pattern pattern) { + acceptMatchers.add(new RegexpClassNameMatcher(pattern)); + return this; + } + + /** + * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) + * FilenameUtils.wildcardMatch} + * @return this instance. + */ + public ObjectStreamClassPredicate accept(final String... patterns) { + Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add); + return this; + } + + /** + * Rejects the specified classes for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param classes Classes to reject + * @return this instance. + */ + public ObjectStreamClassPredicate reject(final Class<?>... classes) { + Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add); + return this; + } + + /** + * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param m the matcher to use + * @return this instance. + */ + public ObjectStreamClassPredicate reject(final ClassNameMatcher m) { + rejectMatchers.add(m); + return this; + } + + /** + * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param pattern standard Java regexp + * @return this instance. + */ + public ObjectStreamClassPredicate reject(final Pattern pattern) { + rejectMatchers.add(new RegexpClassNameMatcher(pattern)); + return this; + } + + /** + * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) + * FilenameUtils.wildcardMatch} + * @return this instance. + */ + public ObjectStreamClassPredicate reject(final String... patterns) { + Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add); + return this; + } + + /** + * Tests that the ObjectStreamClass conforms to requirements. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param objectStreamClass The ObjectStreamClass to test. + * @return true if the input is accepted, false if rejected, false if neither. + */ + @Override + public boolean test(final ObjectStreamClass objectStreamClass) { + return test(objectStreamClass.getName()); + } + + /** + * Tests that the class name conforms to requirements. + * <p> + * The reject list takes precedence over the accept list. + * </p> + * + * @param name The class name to test. + * @return true if the input is accepted, false if rejected, false if neither. + */ + public boolean test(final String name) { + // The reject list takes precedence over the accept list. + for (final ClassNameMatcher m : rejectMatchers) { + if (m.matches(name)) { + return false; + } + } + for (final ClassNameMatcher m : acceptMatchers) { + if (m.matches(name)) { + return true; + } + } + return false; } } @@ -110,8 +434,7 @@ public class ValidatingObjectInputStream extends ObjectInputStream { return new Builder(); } - private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>(); - private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>(); + private final ObjectStreamClassPredicate predicate; /** * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be @@ -119,80 +442,94 @@ public class ValidatingObjectInputStream extends ObjectInputStream { * * @param input an input stream * @throws IOException if an I/O error occurs while reading stream header - * @deprecated Use {@link Builder}. + * @deprecated Use {@link #builder()}. */ @Deprecated public ValidatingObjectInputStream(final InputStream input) throws IOException { + this(input, new ObjectStreamClassPredicate()); + } + + /** + * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be + * deserialized, as by default no classes are accepted. + * + * @param input an input stream. + * @param predicate how to accept and reject classes. + * @throws IOException if an I/O error occurs while reading stream header. + */ + private ValidatingObjectInputStream(final InputStream input, final ObjectStreamClassPredicate predicate) throws IOException { super(input); + this.predicate = predicate; } /** * Accepts the specified classes for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param classes Classes to accept - * @return this object + * @return this instance. */ public ValidatingObjectInputStream accept(final Class<?>... classes) { - Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add); + predicate.accept(classes); return this; } /** * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> * - * @param matcher the class name matcher to <em>accept</em> objects. + * @param matcher a class name matcher to <em>accept</em> objects. * @return this instance. */ public ValidatingObjectInputStream accept(final ClassNameMatcher matcher) { - acceptMatchers.add(matcher); + predicate.accept(matcher); return this; } /** * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param pattern a Pattern for compiled regular expression. * @return this instance. */ public ValidatingObjectInputStream accept(final Pattern pattern) { - acceptMatchers.add(new RegexpClassNameMatcher(pattern)); + predicate.accept(pattern); return this; } /** * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) * FilenameUtils.wildcardMatch}. * @return this instance. */ public ValidatingObjectInputStream accept(final String... patterns) { - Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add); + predicate.accept(patterns); return this; } /** * Checks that the class name conforms to requirements. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param name The class name to test. * @throws InvalidClassException Thrown when a rejected or non-accepted class is found. */ private void checkClassName(final String name) throws InvalidClassException { - // Reject has precedence over accept - for (final ClassNameMatcher m : rejectMatchers) { - if (m.matches(name)) { - invalidClassNameFound(name); - } - } - - boolean ok = false; - for (final ClassNameMatcher m : acceptMatchers) { - if (m.matches(name)) { - ok = true; - break; - } - } - if (!ok) { + if (!predicate.test(name)) { invalidClassNameFound(name); } } @@ -208,48 +545,75 @@ public class ValidatingObjectInputStream extends ObjectInputStream { throw new InvalidClassException("Class name not accepted: " + className); } + /** + * Delegates to {@link #readObject()} and casts to the generic {@code T}. + * + * @param <T> The return type. + * @return Result from {@link #readObject()}. + * @throws ClassNotFoundException Thrown by {@link #readObject()}. + * @throws IOException Thrown by {@link #readObject()}. + * @throws ClassCastException Thrown when {@link #readObject()} does not match {@code T}. + * @since 2.18.0 + */ + @SuppressWarnings("unchecked") + public <T> T readObjectCast() throws ClassNotFoundException, IOException { + return (T) super.readObject(); + } + /** * Rejects the specified classes for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param classes Classes to reject. * @return this instance. */ public ValidatingObjectInputStream reject(final Class<?>... classes) { - Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add); + predicate.reject(classes); return this; } /** * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param matcher a class name matcher to <em>reject</em> objects. * @return this instance. */ public ValidatingObjectInputStream reject(final ClassNameMatcher matcher) { - rejectMatchers.add(matcher); + predicate.reject(matcher); return this; } /** * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param pattern a Pattern for compiled regular expression. * @return this instance. */ public ValidatingObjectInputStream reject(final Pattern pattern) { - rejectMatchers.add(new RegexpClassNameMatcher(pattern)); + predicate.reject(pattern); return this; } /** * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted. + * <p> + * The reject list takes precedence over the accept list. + * </p> * * @param patterns An array of wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) * FilenameUtils.wildcardMatch} * @return this instance. */ public ValidatingObjectInputStream reject(final String... patterns) { - Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add); + predicate.reject(patterns); return this; } diff --git a/src/site/xdoc/description.xml b/src/site/xdoc/description.xml index 405b96a14..75fdd18cf 100644 --- a/src/site/xdoc/description.xml +++ b/src/site/xdoc/description.xml @@ -245,7 +245,19 @@ limitations under the License. ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setInputStream(bais).get()) { // String.class is automatically accepted vois.accept(HashMap.class, Number.class, Integer.class); - final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); + final HashMap<String, Integer< map2 = (HashMap<String, Integer>) vois.readObject(); + assertEquals(map1, map2); + } + // Reusing a configuration + final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate() + .accept(HashMap.class, Number.class, Integer.class); + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .setPredicate(predicate) + .setInputStream(bais) + .get()) { + // String.class is automatically accepted + final HashMap<String, Integer< map2 = (HashMap<String, Integer<) vois.readObject(); assertEquals(map1, map2); } </source> diff --git a/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java b/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java index 7c67d3525..aef6d4218 100644 --- a/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java +++ b/src/test/java/org/apache/commons/io/serialization/MoreComplexObjectTest.java @@ -61,11 +61,15 @@ public class MoreComplexObjectTest extends AbstractCloseableListTest { */ @Test public void testTrustJavaIncludingArrays() throws IOException, ClassNotFoundException { + // @formatter:off assertSerialization(addCloseable( - new ValidatingObjectInputStream(inputStream) + ValidatingObjectInputStream.builder() + .setInputStream(inputStream) .accept(MoreComplexObject.class) .accept("java.*", "[Ljava.*") + .get() )); + // @formatter:on } /** @@ -74,11 +78,15 @@ public class MoreComplexObjectTest extends AbstractCloseableListTest { */ @Test public void testTrustJavaLang() throws IOException, ClassNotFoundException { + // @formatter:off assertSerialization(addCloseable( - new ValidatingObjectInputStream(inputStream) + ValidatingObjectInputStream.builder() + .setInputStream(inputStream) .accept(MoreComplexObject.class, ArrayList.class, Random.class) .accept("java.lang.*", "[Ljava.lang.*") + .get() )); + // @formatter:on } /** @@ -94,10 +102,14 @@ public class MoreComplexObjectTest extends AbstractCloseableListTest { "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.beans.factory.ObjectFactory" }; + // @formatter:off assertSerialization(addCloseable( - new ValidatingObjectInputStream(inputStream) + ValidatingObjectInputStream.builder() + .setInputStream(inputStream) .accept("*") .reject(blacklist) + .get() )); + // @formatter:on } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java index 75b204bcb..0d92908bf 100644 --- a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStreamTest.java @@ -33,6 +33,9 @@ import java.util.HashMap; import java.util.UUID; import java.util.regex.Pattern; +import org.apache.commons.io.serialization.ValidatingObjectInputStream.Builder; +import org.apache.commons.io.serialization.ValidatingObjectInputStream.ObjectStreamClassPredicate; +import org.apache.commons.lang3.SerializationUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,8 +54,12 @@ public class ValidatingObjectInputStreamTest extends AbstractCloseableListTest { assertEquals(testObject, result); } + private Builder newBuilder() { + return ValidatingObjectInputStream.builder().setInputStream(testStream); + } + private ValidatingObjectInputStream newFixture() throws IOException { - return ValidatingObjectInputStream.builder().setInputStream(testStream).get(); + return newBuilder().get(); } @BeforeEach @@ -65,54 +72,55 @@ public class ValidatingObjectInputStreamTest extends AbstractCloseableListTest { } @Test - public void testAcceptCustomMatcher() throws Exception { + public void testAcceptCustomMatcherBuilder() throws Exception { + assertSerialization(addCloseable(newBuilder().accept(ALWAYS_TRUE).get())); + } + + @Test + public void testAcceptCustomMatcherInstance() throws Exception { assertSerialization(addCloseable(newFixture()).accept(ALWAYS_TRUE)); } @Test - public void testAcceptPattern() throws Exception { - assertSerialization(addCloseable(newFixture()).accept(Pattern.compile(".*MockSerializedClass.*"))); + public void testAcceptOneFail() throws Exception { + assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(Integer.class))); } @Test - public void testAcceptWildcard() throws Exception { - assertSerialization(addCloseable(newFixture()).accept("org.apache.commons.io.*")); + public void testAcceptOnePassBuilder() throws Exception { + assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).get())); } @Test - public void testAcceptOnePass() throws Exception { + public void testAcceptOnePassInstance() throws Exception { assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class)); } @Test - public void testAcceptOneFail() throws Exception { - assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(Integer.class))); + public void testAcceptPatternBuilder() throws Exception { + assertSerialization(addCloseable(newBuilder().accept(Pattern.compile(".*MockSerializedClass.*")).get())); } - /** - * Javadoc example. - */ - @SuppressWarnings({ "unchecked", "resource" }) @Test - public void testAcceptExample() throws Exception { - // Data - final HashMap<String, Integer> map1 = new HashMap<>(); - map1.put("1", 1); - // Write - final byte[] byteArray; - try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final ObjectOutputStream oos = new ObjectOutputStream(baos)) { - oos.writeObject(map1); - oos.flush(); - byteArray = baos.toByteArray(); - } - // Read - try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); - ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setInputStream(bais).get()) { - // String.class is automatically accepted - vois.accept(HashMap.class, Number.class, Integer.class); - final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); - assertEquals(map1, map2); + public void testAcceptPatternInstance() throws Exception { + assertSerialization(addCloseable(newFixture()).accept(Pattern.compile(".*MockSerializedClass.*"))); + } + + @Test + public void testAcceptWildcardBuilder() throws Exception { + assertSerialization(addCloseable(newBuilder().accept("org.apache.commons.io.*").get())); + } + + @Test + public void testAcceptWildcardInstance() throws Exception { + assertSerialization(addCloseable(newFixture()).accept("org.apache.commons.io.*")); + } + + @Test + public void testBuildDefault() throws Exception { + final byte[] serialized = SerializationUtils.serialize(""); + try (InputStream is = newBuilder().setInputStream(new ByteArrayInputStream(serialized)).get()) { + // empty } } @@ -144,6 +152,49 @@ public class ValidatingObjectInputStreamTest extends AbstractCloseableListTest { assertTrue(ice.getMessage().contains(name), "Expecting message to contain " + name); } + /** + * Javadoc example. + */ + @SuppressWarnings({ "unchecked" }) + @Test + public void testJavadocExample() throws Exception { + // @formatter:off + // Defining Object fixture + final HashMap<String, Integer> map1 = new HashMap<>(); + map1.put("1", 1); + // Writing serialized fixture + final byte[] byteArray; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(map1); + oos.flush(); + byteArray = baos.toByteArray(); + } + // Reading + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .accept(HashMap.class, Number.class, Integer.class) + .setInputStream(bais) + .get()) { + // String.class is automatically accepted + final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); + assertEquals(map1, map2); + } + // Reusing a configuration + final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate() + .accept(HashMap.class, Number.class, Integer.class); + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder() + .setPredicate(predicate) + .setInputStream(bais) + .get()) { + // String.class is automatically accepted + final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject(); + assertEquals(map1, map2); + } + // @formatter:on + } + @Test public void testNoAccept() { assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()))); @@ -180,16 +231,28 @@ public class ValidatingObjectInputStreamTest extends AbstractCloseableListTest { } @Test - public void testReject() { + public void testRejectBuilder() { assertThrows(InvalidClassException.class, - () -> assertSerialization(addCloseable(newFixture()).accept(Long.class).reject(MockSerializedClass.class, Integer.class))); + () -> assertSerialization(addCloseable(newBuilder().accept(Long.class).reject(MockSerializedClass.class, Integer.class).get()))); + } + + @Test + public void testRejectCustomMatcherBuilder() { + assertThrows(InvalidClassException.class, + () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(ALWAYS_TRUE).get()))); } @Test - public void testRejectCustomMatcher() { + public void testRejectCustomMatcherInstance() { assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(ALWAYS_TRUE))); } + @Test + public void testRejectInstance() { + assertThrows(InvalidClassException.class, + () -> assertSerialization(addCloseable(newFixture()).accept(Long.class).reject(MockSerializedClass.class, Integer.class))); + } + @Test public void testRejectOnly() { assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).reject(Integer.class))); @@ -202,13 +265,64 @@ public class ValidatingObjectInputStreamTest extends AbstractCloseableListTest { } @Test - public void testRejectPrecedence() { + public void testRejectPrecedenceBuilder() { + assertThrows(InvalidClassException.class, + () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(MockSerializedClass.class, Integer.class).get()))); + } + + @Test + public void testRejectPrecedenceInstance() { assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(MockSerializedClass.class, Integer.class))); } @Test - public void testRejectWildcard() { + public void testRejectWildcardBuilder() { + assertThrows(InvalidClassException.class, + () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject("org.*").get()))); + } + + @Test + public void testRejectWildcardInstance() { assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject("org.*"))); } + + @Test + public void testReuseConfiguration() throws Exception { + // Defining Object fixture + final HashMap<String, Integer> map1 = new HashMap<>(); + map1.put("1", 1); + // Writing serialized fixture + final byte[] byteArray; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(map1); + oos.flush(); + byteArray = baos.toByteArray(); + } + // Reusing a configuration: ObjectStreamClassPredicate + final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate().accept(HashMap.class, Number.class, Integer.class); + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get()) { + // String.class is automatically accepted + assertEquals(map1, vois.readObjectCast()); + } + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get()) { + // String.class is automatically accepted + assertEquals(map1, vois.readObjectCast()); + } + // Reusing a configuration: Builder and ObjectStreamClassPredicate + final Builder builder = ValidatingObjectInputStream.builder().setPredicate(predicate); + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = builder.setInputStream(bais).get()) { + // String.class is automatically accepted + assertEquals(map1, vois.readObjectCast()); + } + try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); + ValidatingObjectInputStream vois = builder.setInputStream(bais).get()) { + // String.class is automatically accepted + assertEquals(map1, vois.readObjectCast()); + } + } }