66
77import com .retailsvc .http .internal .DispatchHandler ;
88import com .retailsvc .http .internal .ExceptionFilter ;
9+ import com .retailsvc .http .internal .FormTypeMapper ;
910import com .retailsvc .http .internal .RequestPreparationFilter ;
1011import com .retailsvc .http .internal .Router ;
12+ import com .retailsvc .http .internal .TextTypeMapper ;
1113import com .retailsvc .http .spec .Spec ;
1214import com .retailsvc .http .validate .DefaultValidator ;
1315import com .sun .net .httpserver .HttpContext ;
@@ -30,47 +32,16 @@ public class OpenApiServer implements AutoCloseable {
3032
3133 private static final Logger LOG = LoggerFactory .getLogger (OpenApiServer .class );
3234 private static final int DEFAULT_PORT = 8080 ;
35+ private static final String JSON = "application/json" ;
36+ private static final String GSON_CLASS = "com.google.gson.Gson" ;
37+ private static final String GSON_MAPPER_CLASS = "com.retailsvc.http.internal.gson.GsonJsonMapper" ;
3338
3439 private final HttpServer httpServer ;
3540 private final int shutdownTimeoutSeconds ;
3641
37- /**
38- * @param spec The parsed {@link Spec}
39- * @param jsonMapper Body deserializer
40- * @param handlers Mappings between operationId and {@link HttpHandler}
41- * @param exceptionHandler Error handler receiving exceptions thrown from a handler
42- * @throws IOException If an error occurs during server start
43- */
44- public OpenApiServer (
45- Spec spec ,
46- JsonMapper jsonMapper ,
47- Map <String , HttpHandler > handlers ,
48- ExceptionHandler exceptionHandler )
49- throws IOException {
50- this (spec , jsonMapper , handlers , exceptionHandler , DEFAULT_PORT , Map .of (), 0 );
51- }
52-
53- /**
54- * @param spec The parsed {@link Spec}
55- * @param jsonMapper Body deserializer
56- * @param handlers Mappings between operationId and {@link HttpHandler}
57- * @param exceptionHandler Error handler receiving exceptions thrown from a handler
58- * @param port The server port to use
59- * @throws IOException If an error occurs during server start
60- */
61- public OpenApiServer (
62- Spec spec ,
63- JsonMapper jsonMapper ,
64- Map <String , HttpHandler > handlers ,
65- ExceptionHandler exceptionHandler ,
66- int port )
67- throws IOException {
68- this (spec , jsonMapper , handlers , exceptionHandler , port , Map .of (), 0 );
69- }
70-
7142 OpenApiServer (
7243 Spec spec ,
73- JsonMapper jsonMapper ,
44+ Map < String , TypeMapper > bodyMappers ,
7445 Map <String , HttpHandler > handlers ,
7546 ExceptionHandler exceptionHandler ,
7647 int port ,
@@ -79,7 +50,7 @@ public OpenApiServer(
7950 throws IOException {
8051
8152 requireNonNull (spec , "Spec must not be null" );
82- requireNonNull (jsonMapper , "JsonMapper must not be null" );
53+ requireNonNull (bodyMappers , "bodyMappers must not be null" );
8354 requireNonNull (handlers , "handlers must not be null" );
8455 if (exceptionHandler == null ) {
8556 LOG .warn ("No ExceptionHandler set, using default" );
@@ -95,7 +66,7 @@ public OpenApiServer(
9566
9667 HttpContext ctx = httpServer .createContext (Optional .ofNullable (spec .basePath ()).orElse ("/" ));
9768 ctx .getFilters ().add (new ExceptionFilter (exceptionHandler ));
98- ctx .getFilters ().add (new RequestPreparationFilter (spec , router , validator , jsonMapper ));
69+ ctx .getFilters ().add (new RequestPreparationFilter (spec , router , validator , bodyMappers ));
9970 ctx .setHandler (new DispatchHandler (handlers ));
10071
10172 for (Map .Entry <String , HttpHandler > e : extras .entrySet ()) {
@@ -144,7 +115,7 @@ public static Builder builder() {
144115 public static final class Builder {
145116
146117 private Spec spec ;
147- private JsonMapper jsonMapper ;
118+ private final LinkedHashMap < String , TypeMapper > bodyMappers = new LinkedHashMap <>() ;
148119 private Map <String , HttpHandler > handlers ;
149120 private ExceptionHandler exceptionHandler ;
150121 private int port = DEFAULT_PORT ;
@@ -158,8 +129,10 @@ public Builder spec(Spec spec) {
158129 return this ;
159130 }
160131
161- public Builder jsonMapper (JsonMapper jsonMapper ) {
162- this .jsonMapper = jsonMapper ;
132+ public Builder bodyMapper (String mediaType , TypeMapper mapper ) {
133+ requireNonNull (mediaType , "mediaType must not be null" );
134+ requireNonNull (mapper , "mapper must not be null" );
135+ bodyMappers .put (mediaType .toLowerCase (java .util .Locale .ROOT ), mapper );
163136 return this ;
164137 }
165138
@@ -204,7 +177,6 @@ public Builder addHandler(String path, HttpHandler handler) {
204177
205178 public OpenApiServer build () throws IOException {
206179 requireNonNull (spec , "Spec must not be null" );
207- requireNonNull (jsonMapper , "JsonMapper must not be null" );
208180 requireNonNull (handlers , "handlers must not be null" );
209181 String basePath = Optional .ofNullable (spec .basePath ()).orElse ("/" );
210182 for (String path : extras .keySet ()) {
@@ -213,8 +185,43 @@ public OpenApiServer build() throws IOException {
213185 "extra handler path " + path + " conflicts with spec basePath " + basePath );
214186 }
215187 }
188+ Map <String , TypeMapper > resolved = resolveBodyMappers (bodyMappers );
216189 return new OpenApiServer (
217- spec , jsonMapper , handlers , exceptionHandler , port , extras , shutdownTimeoutSeconds );
190+ spec , resolved , handlers , exceptionHandler , port , extras , shutdownTimeoutSeconds );
191+ }
192+
193+ private static Map <String , TypeMapper > resolveBodyMappers (
194+ Map <String , TypeMapper > userSupplied ) {
195+ LinkedHashMap <String , TypeMapper > out = new LinkedHashMap <>();
196+ out .put ("application/x-www-form-urlencoded" , new FormTypeMapper ());
197+ out .put ("text/plain" , new TextTypeMapper ());
198+ out .putAll (userSupplied );
199+ if (!out .containsKey (JSON )) {
200+ TypeMapper fallback = tryLoadGsonMapper ();
201+ if (fallback != null ) {
202+ out .put (JSON , fallback );
203+ }
204+ }
205+ if (!out .containsKey (JSON )) {
206+ throw new IllegalStateException (
207+ "No TypeMapper registered for application/json and Gson not found on classpath; "
208+ + "register one via Builder.bodyMapper(\" application/json\" , ...)" );
209+ }
210+ return out ;
211+ }
212+
213+ private static TypeMapper tryLoadGsonMapper () {
214+ try {
215+ Class .forName (GSON_CLASS , false , OpenApiServer .class .getClassLoader ());
216+ } catch (ClassNotFoundException _) {
217+ return null ;
218+ }
219+ try {
220+ Class <?> cls = Class .forName (GSON_MAPPER_CLASS , true , OpenApiServer .class .getClassLoader ());
221+ return (TypeMapper ) cls .getDeclaredConstructor ().newInstance ();
222+ } catch (ReflectiveOperationException e ) {
223+ throw new IllegalStateException ("Failed to load " + GSON_MAPPER_CLASS , e );
224+ }
218225 }
219226 }
220227}
0 commit comments