From 66e840a9591804cf74a350524254939c5886d329 Mon Sep 17 00:00:00 2001 From: wololock Date: Tue, 25 Nov 2014 13:49:43 +0100 Subject: [PATCH 1/3] added ChainSchemaFactoryWrapper with BeanPropertyFilter chain This change provides an extended SchemaFactoryWrapper with BeanProperty filtering option - thanks that you can simply exclude properties of existing model from being added to JSON schema (e.g. you can skip @Deprecated fields or any custom annotated field). --- .../ChainSchemaFactoryWrapper.java | 63 +++++++ .../ChainSchemaFactoryWrapperFactory.java | 47 +++++ .../filter/BeanPropertyFilter.java | 13 ++ .../RuntimeAnnotatedBeanPropertyFilter.java | 44 +++++ .../factories/ObjectVisitorDecorator.java | 57 ++++++ .../TestChainSchemaFactoryWrapper.java | 177 ++++++++++++++++++ 6 files changed, 401 insertions(+) create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/BeanPropertyFilter.java create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/RuntimeAnnotatedBeanPropertyFilter.java create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/factories/ObjectVisitorDecorator.java create mode 100644 src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java new file mode 100644 index 00000000..772a12cc --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java @@ -0,0 +1,63 @@ +package com.fasterxml.jackson.module.jsonSchema.customProperties; + +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; +import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.factories.ObjectVisitor; +import com.fasterxml.jackson.module.jsonSchema.factories.ObjectVisitorDecorator; +import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; + +import java.util.Iterator; +import java.util.List; + +/** + * This subtype of {@link com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper} allows + * you to filter out {@link com.fasterxml.jackson.databind.BeanProperty} from generating schema by applying + * to each property {@link com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter}. + * + * BeanProperty will be excluded if at least one filter excludes it. + * + * @author wololock + */ +public class ChainSchemaFactoryWrapper extends SchemaFactoryWrapper { + + private final List filters; + + public ChainSchemaFactoryWrapper(ChainSchemaFactoryWrapperFactory wrapperFactory) { + super(wrapperFactory); + this.filters = wrapperFactory.getFilters(); + } + + @Override + public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { + return new ObjectVisitorDecorator((ObjectVisitor) super.expectObjectFormat(convertedType)) { + @Override + public void optionalProperty(BeanProperty writer) throws JsonMappingException { + boolean allowed = applyFilters(writer); + if (allowed) { + super.optionalProperty(writer); + } + } + + @Override + public void property(BeanProperty writer) throws JsonMappingException { + boolean allowed = applyFilters(writer); + if (allowed) { + super.property(writer); + } + } + + private boolean applyFilters(BeanProperty writer) { + boolean allowed = true; + Iterator iterator = filters.iterator(); + while (iterator.hasNext() && allowed) { + allowed = iterator.next().test(writer); + } + return allowed; + } + }; + } + +} diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java new file mode 100644 index 00000000..3f1e365c --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java @@ -0,0 +1,47 @@ +package com.fasterxml.jackson.module.jsonSchema.customProperties; + +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; +import com.fasterxml.jackson.module.jsonSchema.factories.VisitorContext; +import com.fasterxml.jackson.module.jsonSchema.factories.WrapperFactory; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Creates {@link com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapper} with + * injected list of {@link com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter} filters. + * + * This class is thread-safe. + * + * @author wololock + */ +public class ChainSchemaFactoryWrapperFactory extends WrapperFactory { + + private final List filters; + + public ChainSchemaFactoryWrapperFactory(List filters) { + this.filters = Collections.unmodifiableList(filters != null ? filters : new LinkedList()); + } + + public List getFilters() { + return filters; + } + + @Override + public SchemaFactoryWrapper getWrapper(SerializerProvider p) { + SchemaFactoryWrapper wrapper = new ChainSchemaFactoryWrapper(this); + wrapper.setProvider(p); + return wrapper; + } + + @Override + public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc) { + SchemaFactoryWrapper wrapper = new ChainSchemaFactoryWrapper(this); + wrapper.setProvider(p); + wrapper.setVisitorContext(rvc); + return wrapper; + } +} diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/BeanPropertyFilter.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/BeanPropertyFilter.java new file mode 100644 index 00000000..90e7ea85 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/BeanPropertyFilter.java @@ -0,0 +1,13 @@ +package com.fasterxml.jackson.module.jsonSchema.customProperties.filter; + +import com.fasterxml.jackson.databind.BeanProperty; + +/** + * Checks if given {@link com.fasterxml.jackson.databind.BeanProperty} + * follows filtering rule. + * + * @author wololock + */ +public interface BeanPropertyFilter { + boolean test(BeanProperty property); +} diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/RuntimeAnnotatedBeanPropertyFilter.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/RuntimeAnnotatedBeanPropertyFilter.java new file mode 100644 index 00000000..52ef2b55 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/filter/RuntimeAnnotatedBeanPropertyFilter.java @@ -0,0 +1,44 @@ +package com.fasterxml.jackson.module.jsonSchema.customProperties.filter; + +import com.fasterxml.jackson.databind.BeanProperty; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Rejects {@link com.fasterxml.jackson.databind.BeanProperty} if it's + * annotated with at least one given annotation. + * + * @author wololock + */ +public class RuntimeAnnotatedBeanPropertyFilter implements BeanPropertyFilter { + + private final List> rejectedAnnotations; + + public RuntimeAnnotatedBeanPropertyFilter(List> rejectedAnnotations) { + this.rejectedAnnotations = Collections.unmodifiableList(rejectedAnnotations); + } + + public RuntimeAnnotatedBeanPropertyFilter(Class ...classes) { + this(Arrays.asList(classes)); + } + + @Override + public boolean test(BeanProperty property) { + boolean accept = true; + if (hasAnnotations(property)) { + Iterator> iterator = rejectedAnnotations.iterator(); + while (accept && iterator.hasNext()) { + accept = !property.getMember().hasAnnotation(iterator.next()); + } + } + return accept; + } + + private boolean hasAnnotations(BeanProperty property) { + return property.getMember().annotations().iterator().hasNext(); + } +} diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/factories/ObjectVisitorDecorator.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/factories/ObjectVisitorDecorator.java new file mode 100644 index 00000000..ae5c794c --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/factories/ObjectVisitorDecorator.java @@ -0,0 +1,57 @@ +package com.fasterxml.jackson.module.jsonSchema.factories; + +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; + +/** + * @author cponomaryov + */ +public class ObjectVisitorDecorator implements JsonObjectFormatVisitor, JsonSchemaProducer { + + protected ObjectVisitor objectVisitor; + + public ObjectVisitorDecorator(ObjectVisitor objectVisitor) { + this.objectVisitor = objectVisitor; + } + + @Override + public JsonSchema getSchema() { + return objectVisitor.getSchema(); + } + + @Override + public SerializerProvider getProvider() { + return objectVisitor.getProvider(); + } + + @Override + public void setProvider(SerializerProvider serializerProvider) { + objectVisitor.setProvider(serializerProvider); + } + + @Override + public void optionalProperty(BeanProperty writer) throws JsonMappingException { + objectVisitor.optionalProperty(writer); + } + + @Override + public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException { + objectVisitor.optionalProperty(name, handler, propertyTypeHint); + } + + @Override + public void property(BeanProperty writer) throws JsonMappingException { + objectVisitor.property(writer); + } + + @Override + public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException { + objectVisitor.property(name, handler, propertyTypeHint); + } + +} \ No newline at end of file diff --git a/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java new file mode 100644 index 00000000..e5463702 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java @@ -0,0 +1,177 @@ +package com.fasterxml.jackson.module.jsonSchema; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapper; +import com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapperFactory; +import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.RuntimeAnnotatedBeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; +import org.junit.Before; +import org.junit.Test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TestChainSchemaFactoryWrapper { + + @Retention(RetentionPolicy.RUNTIME) + private static @interface FilterThatOne { + } + + private static class InternalValue { + private BigDecimal value; + + private boolean required; + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + + @FilterThatOne + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + } + + private static class TestBean { + private String name; + + @FilterThatOne + private String oldName; + + private InternalValue internalValue; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getOldName() { + return oldName; + } + + public void setOldName(String oldName) { + this.oldName = oldName; + } + + public InternalValue getInternalValue() { + return internalValue; + } + + public void setInternalValue(InternalValue internalValue) { + this.internalValue = internalValue; + } + } + + private ChainSchemaFactoryWrapperFactory factory; + + private ChainSchemaFactoryWrapper visitor; + + private ObjectMapper objectMapper; + + private JsonSchema jsonSchema; + + @Before + public void setup() throws JsonMappingException { + factory = new ChainSchemaFactoryWrapperFactory( + Arrays.asList( + new RuntimeAnnotatedBeanPropertyFilter(FilterThatOne.class, Deprecated.class), + + // This test filter removes properties which names start + // with 'value' + new BeanPropertyFilter() { + @Override + public boolean test(BeanProperty property) { + return !property.getName().startsWith("value"); + } + } + ) + ); + + visitor = new ChainSchemaFactoryWrapper(factory); + + objectMapper = new ObjectMapper(); + objectMapper.acceptJsonFormatVisitor(TestBean.class, visitor); + + jsonSchema = visitor.finalSchema(); + } + + + @Test + public void shouldFilterOutOldNamePropertyOfTestBeanClass() throws JsonProcessingException { + //given: + ObjectSchema objectSchema = jsonSchema.asObjectSchema(); + //when: + Map properties = objectSchema.getProperties(); + //then: + assertFalse(properties.containsKey("oldName")); + } + + + @Test + public void shouldNotFilterNamePropertyOfTestBeanClass() { + //given: + ObjectSchema objectSchema = jsonSchema.asObjectSchema(); + //when: + Map properties = objectSchema.getProperties(); + //then: + assertTrue(properties.containsKey("name")); + } + + @Test + public void shouldNotFilterInternalValuePropertyOfTestBeanClass() { + //given: + ObjectSchema objectSchema = jsonSchema.asObjectSchema(); + //when: + Map properties = objectSchema.getProperties(); + //then: + assertTrue(properties.containsKey("internalValue")); + } + + @Test + public void shouldFilterOutRequiredPropertyOfInternalValueClass() { + //given: + ObjectSchema objectSchema = jsonSchema.asObjectSchema() + .getProperties() + .get("internalValue") + .asObjectSchema(); + //when: + Map properties = objectSchema.getProperties(); + //then: + assertFalse(properties.containsKey("required")); + } + + @Test + public void shouldFilterOutValuePropertyOfInternalValueClass() { + //given: + ObjectSchema objectSchema = jsonSchema.asObjectSchema() + .getProperties() + .get("internalValue") + .asObjectSchema(); + //when: + Map properties = objectSchema.getProperties(); + //then: + assertFalse(properties.containsKey("value")); + } +} From 4006352495dd2b37b373dc786334f0d7711035a8 Mon Sep 17 00:00:00 2001 From: wololock Date: Tue, 25 Nov 2014 14:55:28 +0100 Subject: [PATCH 2/3] FilterChainSchemaFactoryWrapper class name refactored --- ...r.java => FilterChainSchemaFactoryWrapper.java} | 4 ++-- ...=> FilterChainSchemaFactoryWrapperFactory.java} | 10 +++++----- ...va => TestFilterChainSchemaFactoryWrapper.java} | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) rename src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/{ChainSchemaFactoryWrapper.java => FilterChainSchemaFactoryWrapper.java} (92%) rename src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/{ChainSchemaFactoryWrapperFactory.java => FilterChainSchemaFactoryWrapperFactory.java} (75%) rename src/test/java/com/fasterxml/jackson/module/jsonSchema/{TestChainSchemaFactoryWrapper.java => TestFilterChainSchemaFactoryWrapper.java} (92%) diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java similarity index 92% rename from src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java rename to src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java index 772a12cc..be650c81 100644 --- a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapper.java +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java @@ -21,11 +21,11 @@ * * @author wololock */ -public class ChainSchemaFactoryWrapper extends SchemaFactoryWrapper { +public class FilterChainSchemaFactoryWrapper extends SchemaFactoryWrapper { private final List filters; - public ChainSchemaFactoryWrapper(ChainSchemaFactoryWrapperFactory wrapperFactory) { + public FilterChainSchemaFactoryWrapper(FilterChainSchemaFactoryWrapperFactory wrapperFactory) { super(wrapperFactory); this.filters = wrapperFactory.getFilters(); } diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java similarity index 75% rename from src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java rename to src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java index 3f1e365c..7e9a0cbd 100644 --- a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/ChainSchemaFactoryWrapperFactory.java +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java @@ -11,18 +11,18 @@ import java.util.List; /** - * Creates {@link com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapper} with + * Creates {@link FilterChainSchemaFactoryWrapper} with * injected list of {@link com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter} filters. * * This class is thread-safe. * * @author wololock */ -public class ChainSchemaFactoryWrapperFactory extends WrapperFactory { +public class FilterChainSchemaFactoryWrapperFactory extends WrapperFactory { private final List filters; - public ChainSchemaFactoryWrapperFactory(List filters) { + public FilterChainSchemaFactoryWrapperFactory(List filters) { this.filters = Collections.unmodifiableList(filters != null ? filters : new LinkedList()); } @@ -32,14 +32,14 @@ public List getFilters() { @Override public SchemaFactoryWrapper getWrapper(SerializerProvider p) { - SchemaFactoryWrapper wrapper = new ChainSchemaFactoryWrapper(this); + SchemaFactoryWrapper wrapper = new FilterChainSchemaFactoryWrapper(this); wrapper.setProvider(p); return wrapper; } @Override public SchemaFactoryWrapper getWrapper(SerializerProvider p, VisitorContext rvc) { - SchemaFactoryWrapper wrapper = new ChainSchemaFactoryWrapper(this); + SchemaFactoryWrapper wrapper = new FilterChainSchemaFactoryWrapper(this); wrapper.setProvider(p); wrapper.setVisitorContext(rvc); return wrapper; diff --git a/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java similarity index 92% rename from src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java rename to src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java index e5463702..e7a5c2ca 100644 --- a/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestChainSchemaFactoryWrapper.java +++ b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java @@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapper; -import com.fasterxml.jackson.module.jsonSchema.customProperties.ChainSchemaFactoryWrapperFactory; +import com.fasterxml.jackson.module.jsonSchema.customProperties.FilterChainSchemaFactoryWrapper; +import com.fasterxml.jackson.module.jsonSchema.customProperties.FilterChainSchemaFactoryWrapperFactory; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.RuntimeAnnotatedBeanPropertyFilter; import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; @@ -22,7 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class TestChainSchemaFactoryWrapper { +public class TestFilterChainSchemaFactoryWrapper { @Retention(RetentionPolicy.RUNTIME) private static @interface FilterThatOne { @@ -84,9 +84,9 @@ public void setInternalValue(InternalValue internalValue) { } } - private ChainSchemaFactoryWrapperFactory factory; + private FilterChainSchemaFactoryWrapperFactory factory; - private ChainSchemaFactoryWrapper visitor; + private FilterChainSchemaFactoryWrapper visitor; private ObjectMapper objectMapper; @@ -94,7 +94,7 @@ public void setInternalValue(InternalValue internalValue) { @Before public void setup() throws JsonMappingException { - factory = new ChainSchemaFactoryWrapperFactory( + factory = new FilterChainSchemaFactoryWrapperFactory( Arrays.asList( new RuntimeAnnotatedBeanPropertyFilter(FilterThatOne.class, Deprecated.class), @@ -109,7 +109,7 @@ public boolean test(BeanProperty property) { ) ); - visitor = new ChainSchemaFactoryWrapper(factory); + visitor = new FilterChainSchemaFactoryWrapper(factory); objectMapper = new ObjectMapper(); objectMapper.acceptJsonFormatVisitor(TestBean.class, visitor); From d353e61013ea66b8dfc315b4d1d2289255ac781a Mon Sep 17 00:00:00 2001 From: wololock Date: Tue, 25 Nov 2014 17:37:28 +0100 Subject: [PATCH 3/3] added optional transformations of filtered bean properties --- .../FilterChainSchemaFactoryWrapper.java | 24 +++++ ...ilterChainSchemaFactoryWrapperFactory.java | 24 ++++- .../transformer/JsonSchemaTransformer.java | 14 +++ .../TestFilterChainSchemaFactoryWrapper.java | 94 +++++++++++++++---- 4 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/transformer/JsonSchemaTransformer.java diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java index be650c81..b67eb910 100644 --- a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapper.java @@ -4,10 +4,13 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.customProperties.transformer.JsonSchemaTransformer; import com.fasterxml.jackson.module.jsonSchema.factories.ObjectVisitor; import com.fasterxml.jackson.module.jsonSchema.factories.ObjectVisitorDecorator; import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; +import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; import java.util.Iterator; import java.util.List; @@ -19,15 +22,21 @@ * * BeanProperty will be excluded if at least one filter excludes it. * + * This wrapper also uses {@link com.fasterxml.jackson.module.jsonSchema.customProperties.transformer.JsonSchemaTransformer} + * transformers to apply some additional transformation of {@link com.fasterxml.jackson.module.jsonSchema.JsonSchema} + * * @author wololock */ public class FilterChainSchemaFactoryWrapper extends SchemaFactoryWrapper { private final List filters; + private final List transformers; + public FilterChainSchemaFactoryWrapper(FilterChainSchemaFactoryWrapperFactory wrapperFactory) { super(wrapperFactory); this.filters = wrapperFactory.getFilters(); + this.transformers = wrapperFactory.getTransformers(); } @Override @@ -38,6 +47,7 @@ public void optionalProperty(BeanProperty writer) throws JsonMappingException { boolean allowed = applyFilters(writer); if (allowed) { super.optionalProperty(writer); + applyTransformations(writer); } } @@ -46,6 +56,7 @@ public void property(BeanProperty writer) throws JsonMappingException { boolean allowed = applyFilters(writer); if (allowed) { super.property(writer); + applyTransformations(writer); } } @@ -57,6 +68,19 @@ private boolean applyFilters(BeanProperty writer) { } return allowed; } + + private void applyTransformations(BeanProperty beanProperty) { + if (!transformers.isEmpty()) { + JsonSchema jsonSchema = getPropertySchema(beanProperty); + for (JsonSchemaTransformer transformer : transformers) { + jsonSchema = transformer.transform(jsonSchema, beanProperty); + } + } + } + + private JsonSchema getPropertySchema(BeanProperty beanProperty) { + return ((ObjectSchema) getSchema()).getProperties().get(beanProperty.getName()); + } }; } diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java index 7e9a0cbd..809c9411 100644 --- a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/FilterChainSchemaFactoryWrapperFactory.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.customProperties.transformer.JsonSchemaTransformer; import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; import com.fasterxml.jackson.module.jsonSchema.factories.VisitorContext; import com.fasterxml.jackson.module.jsonSchema.factories.WrapperFactory; @@ -12,7 +13,9 @@ /** * Creates {@link FilterChainSchemaFactoryWrapper} with - * injected list of {@link com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter} filters. + * injected list of {@link com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter} filters + * and additional list of {@link com.fasterxml.jackson.module.jsonSchema.customProperties.transformer.JsonSchemaTransformer} + * transformers. * * This class is thread-safe. * @@ -20,16 +23,33 @@ */ public class FilterChainSchemaFactoryWrapperFactory extends WrapperFactory { + /** + * Chain of filters + * + * Only properties that match all filters will be included in final + * JSON schema. + */ private final List filters; - public FilterChainSchemaFactoryWrapperFactory(List filters) { + /** + * Additional transformations that have to be applied to filtered + * bean properties. + */ + private final List transformers; + + public FilterChainSchemaFactoryWrapperFactory(List filters, List transformers) { this.filters = Collections.unmodifiableList(filters != null ? filters : new LinkedList()); + this.transformers = Collections.unmodifiableList(transformers != null ? transformers : new LinkedList()); } public List getFilters() { return filters; } + public List getTransformers() { + return transformers; + } + @Override public SchemaFactoryWrapper getWrapper(SerializerProvider p) { SchemaFactoryWrapper wrapper = new FilterChainSchemaFactoryWrapper(this); diff --git a/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/transformer/JsonSchemaTransformer.java b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/transformer/JsonSchemaTransformer.java new file mode 100644 index 00000000..ce55dccc --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/module/jsonSchema/customProperties/transformer/JsonSchemaTransformer.java @@ -0,0 +1,14 @@ +package com.fasterxml.jackson.module.jsonSchema.customProperties.transformer; + +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; + +/** + * JsonSchemaTransformer defines additional {@link com.fasterxml.jackson.module.jsonSchema.JsonSchema} + * transformation. + * + * @author wololock + */ +public interface JsonSchemaTransformer { + JsonSchema transform(JsonSchema jsonSchema, BeanProperty beanProperty); +} diff --git a/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java index e7a5c2ca..0323424c 100644 --- a/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java +++ b/src/test/java/com/fasterxml/jackson/module/jsonSchema/TestFilterChainSchemaFactoryWrapper.java @@ -9,21 +9,30 @@ import com.fasterxml.jackson.module.jsonSchema.customProperties.FilterChainSchemaFactoryWrapperFactory; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.BeanPropertyFilter; import com.fasterxml.jackson.module.jsonSchema.customProperties.filter.RuntimeAnnotatedBeanPropertyFilter; +import com.fasterxml.jackson.module.jsonSchema.customProperties.transformer.JsonSchemaTransformer; import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; +import com.fasterxml.jackson.module.jsonSchema.types.StringSchema; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.math.BigDecimal; import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.UUID; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +/** + * @author wololock + */ public class TestFilterChainSchemaFactoryWrapper { + private static final String EXPECTED_NAME_ID = UUID.randomUUID().toString(); + @Retention(RetentionPolicy.RUNTIME) private static @interface FilterThatOne { } @@ -59,6 +68,8 @@ private static class TestBean { private InternalValue internalValue; + private String lastName; + public String getName() { return name; } @@ -82,6 +93,14 @@ public InternalValue getInternalValue() { public void setInternalValue(InternalValue internalValue) { this.internalValue = internalValue; } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } } private FilterChainSchemaFactoryWrapperFactory factory; @@ -94,29 +113,50 @@ public void setInternalValue(InternalValue internalValue) { @Before public void setup() throws JsonMappingException { - factory = new FilterChainSchemaFactoryWrapperFactory( - Arrays.asList( - new RuntimeAnnotatedBeanPropertyFilter(FilterThatOne.class, Deprecated.class), - - // This test filter removes properties which names start - // with 'value' - new BeanPropertyFilter() { - @Override - public boolean test(BeanProperty property) { - return !property.getName().startsWith("value"); - } + + List filters = Arrays.asList( + new RuntimeAnnotatedBeanPropertyFilter(FilterThatOne.class, Deprecated.class), + + // This test filter removes properties which names start + // with 'value' + new BeanPropertyFilter() { + @Override + public boolean test(BeanProperty property) { + return !property.getName().startsWith("value"); + } + } + ); + + List transformers = Arrays.asList( + new JsonSchemaTransformer() { + @Override + public JsonSchema transform(JsonSchema jsonSchema, BeanProperty beanProperty) { + if (jsonSchema.isStringSchema() && "name".equals(beanProperty.getName())) { + StringSchema stringSchema = jsonSchema.asStringSchema(); + stringSchema.setId(EXPECTED_NAME_ID); } - ) + return jsonSchema; + } + } ); + factory = new FilterChainSchemaFactoryWrapperFactory(filters, transformers); visitor = new FilterChainSchemaFactoryWrapper(factory); - objectMapper = new ObjectMapper(); objectMapper.acceptJsonFormatVisitor(TestBean.class, visitor); - jsonSchema = visitor.finalSchema(); } + /** + * Un-ignore this test if you want to see how final json schema looks like. + * @throws JsonProcessingException + */ + @Test + @Ignore + public void shouldPrintlnGeneratedJsonSchema() throws JsonProcessingException { + System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema)); + } + @Test public void shouldFilterOutOldNamePropertyOfTestBeanClass() throws JsonProcessingException { @@ -174,4 +214,26 @@ public void shouldFilterOutValuePropertyOfInternalValueClass() { //then: assertFalse(properties.containsKey("value")); } + + @Test + public void shouldAddStringIdToTheNamePropertyOfTestBeanClass() { + //when: + StringSchema stringSchema = jsonSchema.asObjectSchema() + .getProperties() + .get("name") + .asStringSchema(); + //then: + assertEquals(EXPECTED_NAME_ID, stringSchema.getId()); + } + + @Test + public void shouldNotAddStringIdToTheLastNamePropertyOfTestBeanClass() { + //when: + StringSchema stringSchema = jsonSchema.asObjectSchema() + .getProperties() + .get("lastName") + .asStringSchema(); + //then: + assertNull(stringSchema.getId()); + } }