Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f1545d4

Browse files
committed
Merge branch 'feature/1490/serializer-deserializer-convention'
fixes springfox#1490
2 parents b1c5b43 + cca290d commit f1545d4

31 files changed

Lines changed: 1119 additions & 505 deletions

File tree

gradle/dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ext {
1111
mapstruct = "1.1.0.Final"
1212
mockito = "1.10.19"
1313
objenesis = "2.5.1"
14+
reflections = "0.9.11"
1415
servlet = "3.1.0"
1516
slf4j = "1.7.24"
1617
snakeyaml = '1.18'

springfox-core/src/main/java/springfox/documentation/schema/AlternateTypeRules.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import java.util.Map;
2828

2929
public class AlternateTypeRules {
30-
public static final int DIRECT_SUBSTITUTION_RULE_ORDER = Ordered.HIGHEST_PRECEDENCE + 1000;
31-
public static final int GENERIC_SUBSTITUTION_RULE_ORDER = Ordered.HIGHEST_PRECEDENCE + 500;
30+
public static final int DIRECT_SUBSTITUTION_RULE_ORDER = Ordered.HIGHEST_PRECEDENCE + 3000;
31+
public static final int GENERIC_SUBSTITUTION_RULE_ORDER = Ordered.HIGHEST_PRECEDENCE + 2000;
3232

3333
private AlternateTypeRules() {
3434
throw new UnsupportedOperationException();

springfox-spring-web/build.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ ext {
1010
}
1111

1212
dependencies {
13+
compile "org.reflections:reflections:$reflections"
1314
compile libs.core
1415
compile libs.spring
15-
provided libs.springProvided
16-
provided libs.clientProvided
17-
1816
compile project(':springfox-spi')
1917
compile project(':springfox-schema').sourceSets.main.output
2018

19+
provided libs.springProvided
20+
provided libs.clientProvided
21+
2122
testCompile project(':springfox-core')
2223
testCompile libs.test
2324
testCompile libs.swagger2Core

springfox-spring-web/src/main/java/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapper.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.context.SmartLifecycle;
2929
import org.springframework.stereotype.Component;
3030
import springfox.documentation.RequestHandler;
31+
import springfox.documentation.schema.AlternateTypeRule;
32+
import springfox.documentation.schema.AlternateTypeRuleConvention;
3133
import springfox.documentation.spi.DocumentationType;
3234
import springfox.documentation.spi.service.DocumentationPlugin;
3335
import springfox.documentation.spi.service.RequestHandlerCombiner;
@@ -43,6 +45,7 @@
4345
import java.util.concurrent.atomic.AtomicBoolean;
4446

4547
import static com.google.common.collect.FluentIterable.*;
48+
import static springfox.documentation.builders.BuilderDefaults.*;
4649
import static springfox.documentation.spi.service.contexts.Orderings.*;
4750

4851
/**
@@ -64,6 +67,8 @@ public class DocumentationPluginsBootstrapper implements SmartLifecycle {
6467

6568
@Autowired(required = false)
6669
private RequestHandlerCombiner combiner;
70+
@Autowired(required = false)
71+
private List<AlternateTypeRuleConvention> typeConventions;
6772

6873
@Autowired
6974
public DocumentationPluginsBootstrapper(
@@ -95,11 +100,24 @@ private DocumentationContextBuilder defaultContextBuilder(DocumentationPlugin pl
95100
List<RequestHandler> requestHandlers = from(handlerProviders)
96101
.transformAndConcat(handlers())
97102
.toList();
103+
List<AlternateTypeRule> rules = from(nullToEmptyList(typeConventions))
104+
.transformAndConcat(toRules())
105+
.toList();
98106
return documentationPluginsManager
99107
.createContextBuilder(documentationType, defaultConfiguration)
108+
.rules(rules)
100109
.requestHandlers(combiner().combine(requestHandlers));
101110
}
102111

112+
private Function<AlternateTypeRuleConvention, List<AlternateTypeRule>> toRules() {
113+
return new Function<AlternateTypeRuleConvention, List<AlternateTypeRule>>() {
114+
@Override
115+
public List<AlternateTypeRule> apply(AlternateTypeRuleConvention input) {
116+
return input.rules();
117+
}
118+
};
119+
}
120+
103121
private RequestHandlerCombiner combiner() {
104122
return Optional.fromNullable(combiner).or(new DefaultRequestHandlerCombiner());
105123
}

springfox-spring-web/src/main/java/springfox/documentation/spring/web/plugins/DocumentationPluginsManager.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ private DocumentationPlugin defaultDocumentationPlugin() {
149149
return new Docket(DocumentationType.SWAGGER_2);
150150
}
151151

152-
public DocumentationContextBuilder createContextBuilder(DocumentationType documentationType,
153-
DefaultConfiguration defaultConfiguration) {
152+
public DocumentationContextBuilder createContextBuilder(
153+
DocumentationType documentationType,
154+
DefaultConfiguration defaultConfiguration) {
154155
return defaultsProviders.getPluginFor(documentationType, defaultConfiguration)
155156
.create(documentationType)
156157
.withResourceGroupingStrategy(resourceGroupingStrategy(documentationType));
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
*
3+
* Copyright 2017-2018 the original author or authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
20+
package springfox.documentation.spring.web.plugins;
21+
22+
import com.fasterxml.classmate.TypeResolver;
23+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
24+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
25+
import com.google.common.base.Function;
26+
import com.google.common.base.Optional;
27+
import com.google.common.collect.Sets;
28+
import org.reflections.Reflections;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
import org.springframework.core.Ordered;
32+
import org.springframework.http.ResponseEntity;
33+
import springfox.documentation.schema.AlternateTypeRule;
34+
import springfox.documentation.schema.AlternateTypeRuleConvention;
35+
36+
import java.lang.reflect.Type;
37+
import java.util.List;
38+
import java.util.Set;
39+
40+
import static com.google.common.collect.Lists.*;
41+
import static springfox.documentation.schema.AlternateTypeRules.*;
42+
43+
/**
44+
* Class to automatically detect type substitutions given the jackson serialize/deserialize annotations
45+
*/
46+
public class JacksonSerializerConvention implements AlternateTypeRuleConvention {
47+
private static final Logger LOGGER = LoggerFactory.getLogger(JacksonSerializerConvention.class);
48+
private static final int IMMUTABLES_CONVENTION_ORDER = Ordered.HIGHEST_PRECEDENCE + 4000;
49+
50+
private final TypeResolver resolver;
51+
private final String packagePrefix;
52+
53+
public JacksonSerializerConvention(TypeResolver resolver, String packagePrefix) {
54+
this.resolver = resolver;
55+
this.packagePrefix = packagePrefix;
56+
}
57+
58+
@Override
59+
public List<AlternateTypeRule> rules() {
60+
List<AlternateTypeRule> rules = newArrayList();
61+
Reflections reflections = new Reflections(packagePrefix);
62+
Set<Class<?>> serialized = reflections.getTypesAnnotatedWith(JsonSerialize.class);
63+
Set<Class<?>> deserialized = reflections.getTypesAnnotatedWith(JsonDeserialize.class);
64+
for (Class<?> type : Sets.union(serialized, deserialized)) {
65+
Optional<Type> found = findAlternate(type);
66+
if (found.isPresent()) {
67+
rules.add(newRule(
68+
resolver.resolve(type),
69+
resolver.resolve(found.get()), getOrder()));
70+
rules.add(newRule(
71+
resolver.resolve(ResponseEntity.class, type),
72+
resolver.resolve(found.get()), getOrder()));
73+
}
74+
}
75+
return rules;
76+
}
77+
78+
private Optional<Type> findAlternate(Class<?> type) {
79+
Class serializer = Optional.fromNullable(type.getAnnotation(JsonSerialize.class))
80+
.transform(new Function<JsonSerialize, Class>() {
81+
@Override
82+
public Class apply(JsonSerialize input) {
83+
return input.as();
84+
}
85+
})
86+
.or(Void.class);
87+
Class deserializer = Optional.fromNullable(type.getAnnotation(JsonDeserialize.class))
88+
.transform(new Function<JsonDeserialize, Class>() {
89+
@Override
90+
public Class apply(JsonDeserialize input) {
91+
return input.as();
92+
}
93+
})
94+
.or(Void.class);
95+
Type toUse;
96+
if (serializer != deserializer) {
97+
LOGGER.warn("The serializer {} and deserializer {} . Picking the serializer by default",
98+
serializer.getName(),
99+
deserializer.getName());
100+
}
101+
if (serializer == Void.class && deserializer == Void.class) {
102+
toUse = null;
103+
} else if (serializer != Void.class) {
104+
toUse = serializer;
105+
} else {
106+
toUse = deserializer;
107+
}
108+
return Optional.fromNullable(toUse);
109+
}
110+
111+
@Override
112+
public int getOrder() {
113+
return IMMUTABLES_CONVENTION_ORDER;
114+
}
115+
}

springfox-spring-web/src/test/groovy/springfox/documentation/spring/web/plugins/DocumentationPluginsBootstrapperSpec.groovy

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ class DocumentationPluginsBootstrapperSpec extends Specification {
4141

4242
DocumentationPluginsBootstrapper bootstrapper =
4343
new DocumentationPluginsBootstrapper(pluginManager,
44-
[handlerProvider],
45-
new DocumentationCache(),
46-
apiGroup,
47-
new TypeResolver(),
48-
new Defaults(), Mock(ServletContext))
44+
[handlerProvider],
45+
new DocumentationCache(),
46+
apiGroup,
47+
new TypeResolver(),
48+
new Defaults(),
49+
Mock(ServletContext))
4950

5051
def setup() {
5152
pluginManager.createContextBuilder(_, _) >> new DocumentationContextBuilder(DocumentationType.SWAGGER_12)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
*
3+
* Copyright 2017-2018 the original author or authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package springfox.documentation.spring.web.plugins
20+
21+
import com.fasterxml.classmate.TypeResolver
22+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
23+
import com.fasterxml.jackson.databind.annotation.JsonSerialize
24+
import org.springframework.stereotype.Component
25+
import spock.lang.Specification
26+
import spock.lang.Unroll
27+
28+
@Component
29+
class JacksonSerializerConventionSpec extends Specification {
30+
def resolver = new TypeResolver()
31+
32+
@Unroll
33+
def "Identifies serializers and deserializers for #original.simpleName" () {
34+
given:
35+
def sut = conventionInThisPackage()
36+
when:
37+
def rules = sut.rules()
38+
then:
39+
rules.find { it.original == resolver.resolve(original) }?.alternate == substitute ?: resolver.resolve(substitute)
40+
41+
where:
42+
original | substitute
43+
Same | A
44+
Different | A
45+
MissingSerialize | B
46+
MissingDeserialize | A
47+
MissingBoth | null
48+
}
49+
50+
def conventionInThisPackage() {
51+
new JacksonSerializerConvention(resolver, "springfox.documentation.spring.web.plugins")
52+
}
53+
54+
@JsonSerialize(as=A)
55+
@JsonDeserialize(as=A)
56+
class Same {
57+
}
58+
59+
@JsonSerialize(as=A)
60+
@JsonDeserialize(as=B)
61+
class Different {
62+
}
63+
64+
@JsonDeserialize(as=B)
65+
class MissingSerialize {
66+
}
67+
68+
@JsonSerialize(as=A)
69+
class MissingDeserialize {
70+
}
71+
72+
class MissingBoth {
73+
}
74+
75+
class A {
76+
}
77+
78+
class B {
79+
}
80+
}

springfox-spring-web/src/test/java/springfox/documentation/spring/web/dummy/controllers/FeatureDemonstrationService.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.web.bind.annotation.RequestMethod;
3737
import org.springframework.web.bind.annotation.RequestParam;
3838
import org.springframework.web.bind.annotation.RequestPart;
39+
import org.springframework.web.bind.annotation.ResponseBody;
3940
import org.springframework.web.multipart.MultipartFile;
4041
import org.springframework.web.util.UriComponentsBuilder;
4142
import springfox.documentation.spring.web.dummy.models.Business;
@@ -48,6 +49,7 @@
4849
import springfox.documentation.spring.web.dummy.models.ModelWithObjectNode;
4950
import springfox.documentation.spring.web.dummy.models.NestedType;
5051
import springfox.documentation.spring.web.dummy.models.Pet;
52+
import springfox.documentation.spring.web.dummy.models.PetWithSerializer;
5153

5254
import java.math.BigDecimal;
5355
import java.util.List;
@@ -212,4 +214,21 @@ public ResponseEntity<Pet> findIdentityById(@PathVariable String itemId) {
212214
public ResponseEntity<FancyPet> findById(@PathVariable String itemId) {
213215
return new ResponseEntity<FancyPet>(new FancyPet(), HttpStatus.OK);
214216
}
217+
218+
//TODO: entity in params doesnt work bug?
219+
@RequestMapping(value = "/1490/entity/{itemId}", method = RequestMethod.GET)
220+
@ResponseBody
221+
public ResponseEntity<PetWithSerializer> serializablePetEntity(@PathVariable String itemId) {
222+
return new ResponseEntity<PetWithSerializer>(new PetWithSerializer(), HttpStatus.OK);
223+
}
224+
225+
@RequestMapping(value = "/1490/{itemId}", method = RequestMethod.GET)
226+
@ResponseBody
227+
public PetWithSerializer serializablePet(@PathVariable String itemId) {
228+
return new PetWithSerializer();
229+
}
230+
231+
@RequestMapping(value = "/1490/{itemId}", method = RequestMethod.PUT)
232+
public void updateSerializablePet(@PathVariable String itemId, @RequestBody PetWithSerializer pet) {
233+
}
215234
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
*
3+
* Copyright 2017-2018 the original author or authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package springfox.documentation.spring.web.dummy.models;
20+
21+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
22+
23+
@JsonSerialize(as= Pet.class)
24+
public class PetWithSerializer {
25+
}

0 commit comments

Comments
 (0)