17
17
18
18
import java .lang .reflect .AnnotatedElement ;
19
19
import java .lang .reflect .Method ;
20
+ import java .util .BitSet ;
20
21
import java .util .List ;
21
22
import java .util .Map ;
22
23
import java .util .TimeZone ;
23
24
import java .util .function .Predicate ;
25
+ import java .util .stream .Stream ;
24
26
25
27
import javax .servlet .http .HttpServletRequest ;
26
28
77
79
import com .fasterxml .jackson .datatype .jsr310 .JavaTimeModule ;
78
80
import com .google .common .base .Charsets ;
79
81
import com .google .common .base .Strings ;
82
+ import com .google .common .cache .CacheBuilder ;
83
+ import com .google .common .cache .CacheLoader ;
84
+ import com .google .common .cache .LoadingCache ;
80
85
import com .google .common .collect .ImmutableMap ;
81
86
import com .google .inject .Provider ;
82
87
94
99
@ PropertySource ("classpath:com/b2international/snowowl/core/rest/service_configuration.properties" )
95
100
public class SnowOwlApiConfig extends WebMvcConfigurationSupport {
96
101
102
+ private static final String INCLUDE_NULL = "includeNull" ;
103
+ private static final int INCLUDE_NULL_IDX = 0 ;
104
+ private static final String PRETTY = "pretty" ;
105
+ private static final int PRETTY_IDX = 1 ;
106
+
97
107
static {
98
108
SpringDocUtils .getConfig ().addResponseWrapperToIgnore (Promise .class );
99
109
}
100
110
101
111
@ Autowired
102
112
private org .springframework .context .ApplicationContext ctx ;
103
113
114
+ private final LoadingCache <BitSet , ObjectMapper > objectMappers = CacheBuilder .newBuilder ().build (new CacheLoader <BitSet , ObjectMapper >() {
115
+ @ Override
116
+ public ObjectMapper load (BitSet configuration ) throws Exception {
117
+ ObjectMapper mapper = createObjectMapper ();
118
+ mapper .setSerializationInclusion (configuration .get (INCLUDE_NULL_IDX ) ? Include .ALWAYS : Include .NON_NULL );
119
+ mapper .configure (SerializationFeature .INDENT_OUTPUT , configuration .get (PRETTY_IDX ));
120
+ return mapper ;
121
+ }
122
+ });
123
+
104
124
@ Bean
105
125
public OpenAPI openAPI () {
106
126
return new OpenAPI ();
107
127
}
108
128
109
- @ Bean
110
- public ObjectMapper objectMapper () {
129
+ public static ObjectMapper createObjectMapper () {
111
130
final ObjectMapper objectMapper = new ObjectMapper ();
112
131
objectMapper .registerModule (new JavaTimeModule ());
113
132
objectMapper .setSerializationInclusion (Include .NON_NULL );
@@ -120,6 +139,42 @@ public ObjectMapper objectMapper() {
120
139
return objectMapper ;
121
140
}
122
141
142
+ @ Bean
143
+ @ Scope (scopeName = WebApplicationContext .SCOPE_REQUEST , proxyMode = ScopedProxyMode .TARGET_CLASS )
144
+ public ObjectMapper objectMapper (@ Autowired HttpServletRequest request ) {
145
+ return objectMappers .getUnchecked (toConfig (
146
+ extractBooleanQueryParameterValue (request , INCLUDE_NULL ),
147
+ extractBooleanQueryParameterValue (request , PRETTY )
148
+ ));
149
+ }
150
+
151
+ private BitSet toConfig (boolean ...serializationFeatureValuesInOrder ) {
152
+ if (serializationFeatureValuesInOrder == null ) {
153
+ return new BitSet (0 );
154
+ }
155
+ BitSet config = new BitSet (serializationFeatureValuesInOrder .length );
156
+ for (int i = 0 ; i < serializationFeatureValuesInOrder .length ; i ++) {
157
+ config .set (i , serializationFeatureValuesInOrder [i ]);
158
+ }
159
+ return config ;
160
+ }
161
+
162
+ private boolean extractBooleanQueryParameterValue (HttpServletRequest request , String queryParameterKey ) {
163
+ String [] values = request .getParameterMap ().containsKey (queryParameterKey ) ? request .getParameterMap ().getOrDefault (queryParameterKey , null ) : null ;
164
+ if (values == null ) {
165
+ // query parameter not present, means disable feature
166
+ return false ;
167
+ } else if (values .length == 0 ) {
168
+ // no values present, but the key is present, enable feature
169
+ return true ;
170
+ } else {
171
+ // XXX due to a bug in jetty-server v9.x, query parameters are duplicated in the low-level request object
172
+ // allowing multiple values for now with empty or valid (true in this case) values here
173
+ // see this bug report for details https://github.com/eclipse/jetty.project/issues/2074
174
+ return Stream .of (values ).allMatch (value -> value .isBlank () || "true" .equals (value ));
175
+ }
176
+ }
177
+
123
178
@ Override
124
179
protected void addReturnValueHandlers (List <HandlerMethodReturnValueHandler > returnValueHandlers ) {
125
180
returnValueHandlers .add (new PromiseMethodReturnValueHandler ());
@@ -204,10 +259,13 @@ public void configureMessageConverters(final List<HttpMessageConverter<?>> conve
204
259
converters .add (new ByteArrayHttpMessageConverter ());
205
260
converters .add (new ResourceHttpMessageConverter ());
206
261
converters .add (new CsvMessageConverter ());
262
+ // XXX using null value here as Spring calls a proxied method anyway which returns an already configured instance, see mapping2JacksonHttpMessageConverter Bean method
263
+ converters .add (mapping2JacksonHttpMessageConverter (null ));
264
+ }
207
265
208
- final MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter ();
209
- jacksonConverter . setObjectMapper ( objectMapper ());
210
- converters . add ( jacksonConverter );
266
+ @ Bean
267
+ public MappingJackson2HttpMessageConverter mapping2JacksonHttpMessageConverter ( ObjectMapper mapper ) {
268
+ return new MappingJackson2HttpMessageConverter ( mapper );
211
269
}
212
270
213
271
@ Override
0 commit comments