001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.swagger; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020import static org.apache.juneau.common.utils.Utils.*; 021import static org.apache.juneau.internal.ClassUtils.*; 022import static org.apache.juneau.internal.CollectionUtils.map; 023import static org.apache.juneau.rest.annotation.RestOpAnnotation.*; 024import static org.apache.juneau.rest.httppart.RestPartType.*; 025 026import java.io.*; 027import java.lang.reflect.*; 028import java.lang.reflect.Method; 029import java.util.*; 030import java.util.function.*; 031 032import org.apache.juneau.*; 033import org.apache.juneau.annotation.*; 034import org.apache.juneau.annotation.Items; 035import org.apache.juneau.bean.swagger.Swagger; 036import org.apache.juneau.collections.*; 037import org.apache.juneau.common.utils.*; 038import org.apache.juneau.cp.*; 039import org.apache.juneau.http.annotation.*; 040import org.apache.juneau.http.annotation.Contact; 041import org.apache.juneau.http.annotation.License; 042import org.apache.juneau.http.annotation.Tag; 043import org.apache.juneau.json.*; 044import org.apache.juneau.jsonschema.*; 045import org.apache.juneau.marshaller.*; 046import org.apache.juneau.parser.*; 047import org.apache.juneau.reflect.*; 048import org.apache.juneau.rest.*; 049import org.apache.juneau.rest.annotation.*; 050import org.apache.juneau.rest.httppart.*; 051import org.apache.juneau.rest.util.*; 052import org.apache.juneau.serializer.*; 053import org.apache.juneau.svl.*; 054 055import jakarta.servlet.*; 056 057/** 058 * A single session of generating a Swagger document. 059 * 060 * <h5 class='section'>See Also:</h5><ul> 061 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanSwagger2">juneau-bean-swagger-v2</a> 062 * </ul> 063 */ 064public class BasicSwaggerProviderSession { 065 066 private final RestContext context; 067 private final Class<?> c; 068 private final ClassInfo rci; 069 private final FileFinder ff; 070 private final Messages mb; 071 private final VarResolverSession vr; 072 private final JsonParser jp = JsonParser.create().ignoreUnknownBeanProperties().build(); 073 private final JsonSchemaGeneratorSession js; 074 private final Locale locale; 075 076 077 /** 078 * Constructor. 079 * 080 * @param context The context of the REST object we're generating Swagger about. 081 * @param locale The language of the swagger we're asking for. 082 * @param ff The file finder to use for finding JSON files. 083 * @param messages The messages to use for finding localized strings. 084 * @param vr The variable resolver to use for resolving variables in the swagger. 085 * @param js The JSON-schema generator to use for stuff like examples. 086 */ 087 public BasicSwaggerProviderSession(RestContext context, Locale locale, FileFinder ff, Messages messages, VarResolverSession vr, JsonSchemaGeneratorSession js) { 088 this.context = context; 089 this.c = context.getResourceClass(); 090 this.rci = ClassInfo.of(c); 091 this.ff = ff; 092 this.mb = messages; 093 this.vr = vr; 094 this.js = js; 095 this.locale = locale; 096 } 097 098 /** 099 * Generates the swagger. 100 * 101 * @return A new {@link Swagger} object. 102 * @throws Exception If an error occurred producing the Swagger. 103 */ 104 public Swagger getSwagger() throws Exception { 105 106 InputStream is = ff.getStream(rci.getSimpleName() + ".json", locale).orElse(null); 107 108 Predicate<String> ne = Utils::isNotEmpty; 109 Predicate<Collection<?>> nec = Utils::isNotEmpty; 110 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 111 112 // Load swagger JSON from classpath. 113 JsonMap omSwagger = Json5.DEFAULT.read(is, JsonMap.class); 114 if (omSwagger == null) 115 omSwagger = new JsonMap(); 116 117 // Combine it with @Rest(swagger) 118 for (Rest rr : rci.getAnnotations(context, Rest.class)) { 119 120 JsonMap sInfo = omSwagger.getMap("info", true); 121 122 sInfo 123 .appendIf(ne, "title", 124 firstNonEmpty( 125 sInfo.getString("title"), 126 resolve(rr.title()) 127 ) 128 ) 129 .appendIf(ne, "description", 130 firstNonEmpty( 131 sInfo.getString("description"), 132 resolve(rr.description()) 133 ) 134 ); 135 136 org.apache.juneau.rest.annotation.Swagger r = rr.swagger(); 137 138 omSwagger.append(parseMap(r.value(), "@Swagger(value) on class {0}", c)); 139 140 if (! SwaggerAnnotation.empty(r)) { 141 JsonMap info = omSwagger.getMap("info", true); 142 143 info 144 .appendIf(ne, "title", resolve(r.title())) 145 .appendIf(ne, "description", resolve(r.description())) 146 .appendIf(ne, "version", resolve(r.version())) 147 .appendIf(ne, "termsOfService", resolve(r.termsOfService())) 148 .appendIf(nem, "contact", 149 merge( 150 info.getMap("contact"), 151 toMap(r.contact(), "@Swagger(contact) on class {0}", c) 152 ) 153 ) 154 .appendIf(nem, "license", 155 merge( 156 info.getMap("license"), 157 toMap(r.license(), "@Swagger(license) on class {0}", c) 158 ) 159 ); 160 } 161 162 omSwagger 163 .appendIf(nem, "externalDocs", 164 merge( 165 omSwagger.getMap("externalDocs"), 166 toMap(r.externalDocs(), "@Swagger(externalDocs) on class {0}", c) 167 ) 168 ) 169 .appendIf(nec, "tags", 170 merge( 171 omSwagger.getList("tags"), 172 toList(r.tags(), "@Swagger(tags) on class {0}", c) 173 ) 174 ); 175 } 176 177 omSwagger.appendIf(nem, "externalDocs", parseMap(mb.findFirstString("externalDocs"), "Messages/externalDocs on class {0}", c)); 178 179 JsonMap info = omSwagger.getMap("info", true); 180 181 info 182 .appendIf(ne, "title", resolve(mb.findFirstString("title"))) 183 .appendIf(ne, "description", resolve(mb.findFirstString("description"))) 184 .appendIf(ne, "version", resolve(mb.findFirstString("version"))) 185 .appendIf(ne, "termsOfService", resolve(mb.findFirstString("termsOfService"))) 186 .appendIf(nem, "contact", parseMap(mb.findFirstString("contact"), "Messages/contact on class {0}", c)) 187 .appendIf(nem, "license", parseMap(mb.findFirstString("license"), "Messages/license on class {0}", c)); 188 189 if (info.isEmpty()) 190 omSwagger.remove("info"); 191 192 JsonList 193 produces = omSwagger.getList("produces", true), 194 consumes = omSwagger.getList("consumes", true); 195 if (consumes.isEmpty()) 196 consumes.addAll(context.getConsumes()); 197 if (produces.isEmpty()) 198 produces.addAll(context.getProduces()); 199 200 Map<String,JsonMap> tagMap = map(); 201 if (omSwagger.containsKey("tags")) { 202 for (JsonMap om : omSwagger.getList("tags").elements(JsonMap.class)) { 203 String name = om.getString("name"); 204 if (name == null) 205 throw new SwaggerException(null, "Tag definition found without name in swagger JSON."); 206 tagMap.put(name, om); 207 } 208 } 209 210 String s = mb.findFirstString("tags"); 211 if (s != null) { 212 for (JsonMap m : parseListOrCdl(s, "Messages/tags on class {0}", c).elements(JsonMap.class)) { 213 String name = m.getString("name"); 214 if (name == null) 215 throw new SwaggerException(null, "Tag definition found without name in resource bundle on class {0}", c) ; 216 if (tagMap.containsKey(name)) 217 tagMap.get(name).putAll(m); 218 else 219 tagMap.put(name, m); 220 } 221 } 222 223 // Load our existing bean definitions into our session. 224 JsonMap definitions = omSwagger.getMap("definitions", true); 225 for (String defId : definitions.keySet()) 226 js.addBeanDef(defId, new JsonMap(definitions.getMap(defId))); 227 228 // Iterate through all the @RestOp methods. 229 for (RestOpContext sm : context.getRestOperations().getOpContexts()) { 230 231 BeanSession bs = sm.getBeanContext().getSession(); 232 233 Method m = sm.getJavaMethod(); 234 MethodInfo mi = MethodInfo.of(m); 235 AnnotationList al = mi.getAnnotationList(REST_OP_GROUP); 236 String mn = m.getName(); 237 238 // Get the operation from the existing swagger so far. 239 JsonMap op = getOperation(omSwagger, sm.getPathPattern(), sm.getHttpMethod().toLowerCase()); 240 241 // Add @RestOp(swagger) 242 Value<OpSwagger> _ms = Value.empty(); 243 al.forEachValue(OpSwagger.class, "swagger", OpSwaggerAnnotation::notEmpty, x -> _ms.set(x)); 244 OpSwagger ms = _ms.orElseGet(()->OpSwaggerAnnotation.create().build()); 245 246 op.append(parseMap(ms.value(), "@OpSwagger(value) on class {0} method {1}", c, m)); 247 op.appendIf(ne, "operationId", 248 firstNonEmpty( 249 resolve(ms.operationId()), 250 op.getString("operationId"), 251 mn 252 ) 253 ); 254 255 Value<String> _summary = Value.empty(); 256 al.forEachValue(String.class, "summary", NOT_EMPTY, x -> _summary.set(x)); 257 op.appendIf(ne, "summary", 258 firstNonEmpty( 259 resolve(ms.summary()), 260 resolve(mb.findFirstString(mn + ".summary")), 261 op.getString("summary"), 262 resolve(_summary.orElse(null)) 263 ) 264 ); 265 266 Value<String[]> _description = Value.empty(); 267 al.forEachValue(String[].class, "description",x -> x.length > 0, x -> _description.set(x)); 268 op.appendIf(ne, "description", 269 firstNonEmpty( 270 resolve(ms.description()), 271 resolve(mb.findFirstString(mn + ".description")), 272 op.getString("description"), 273 resolve(_description.orElse(new String[0])) 274 ) 275 ); 276 op.appendIf(ne, "deprecated", 277 firstNonEmpty( 278 resolve(ms.deprecated()), 279 (m.getAnnotation(Deprecated.class) != null || m.getDeclaringClass().getAnnotation(Deprecated.class) != null) ? "true" : null 280 ) 281 ); 282 op.appendIf(nec, "tags", 283 merge( 284 parseListOrCdl(mb.findFirstString(mn + ".tags"), "Messages/tags on class {0} method {1}", c, m), 285 parseListOrCdl(ms.tags(), "@OpSwagger(tags) on class {0} method {1}", c, m) 286 ) 287 ); 288 op.appendIf(nec, "schemes", 289 merge( 290 parseListOrCdl(mb.findFirstString(mn + ".schemes"), "Messages/schemes on class {0} method {1}", c, m), 291 parseListOrCdl(ms.schemes(), "@OpSwagger(schemes) on class {0} method {1}", c, m) 292 ) 293 ); 294 op.appendIf(nec, "consumes", 295 firstNonEmpty( 296 parseListOrCdl(mb.findFirstString(mn + ".consumes"), "Messages/consumes on class {0} method {1}", c, m), 297 parseListOrCdl(ms.consumes(), "@OpSwagger(consumes) on class {0} method {1}", c, m) 298 ) 299 ); 300 op.appendIf(nec, "produces", 301 firstNonEmpty( 302 parseListOrCdl(mb.findFirstString(mn + ".produces"), "Messages/produces on class {0} method {1}", c, m), 303 parseListOrCdl(ms.produces(), "@OpSwagger(produces) on class {0} method {1}", c, m) 304 ) 305 ); 306 op.appendIf(nec, "parameters", 307 merge( 308 parseList(mb.findFirstString(mn + ".parameters"), "Messages/parameters on class {0} method {1}", c, m), 309 parseList(ms.parameters(), "@OpSwagger(parameters) on class {0} method {1}", c, m) 310 ) 311 ); 312 op.appendIf(nem, "responses", 313 merge( 314 parseMap(mb.findFirstString(mn + ".responses"), "Messages/responses on class {0} method {1}", c, m), 315 parseMap(ms.responses(), "@OpSwagger(responses) on class {0} method {1}", c, m) 316 ) 317 ); 318 op.appendIf(nem, "externalDocs", 319 merge( 320 op.getMap("externalDocs"), 321 parseMap(mb.findFirstString(mn + ".externalDocs"), "Messages/externalDocs on class {0} method {1}", c, m), 322 toMap(ms.externalDocs(), "@OpSwagger(externalDocs) on class {0} method {1}", c, m) 323 ) 324 ); 325 326 if (op.containsKey("tags")) 327 for (String tag : op.getList("tags").elements(String.class)) 328 if (! tagMap.containsKey(tag)) 329 tagMap.put(tag, JsonMap.of("name", tag)); 330 331 JsonMap paramMap = new JsonMap(); 332 if (op.containsKey("parameters")) 333 for (JsonMap param : op.getList("parameters").elements(JsonMap.class)) 334 paramMap.put(param.getString("in") + '.' + ("body".equals(param.getString("in")) ? "body" : param.getString("name")), param); 335 336 // Finally, look for parameters defined on method. 337 for (ParamInfo mpi : mi.getParams()) { 338 339 ClassInfo pt = mpi.getParameterType(); 340 Type type = pt.innerType(); 341 342 if (mpi.hasAnnotation(Content.class) || pt.hasAnnotation(Content.class)) { 343 JsonMap param = paramMap.getMap(BODY + ".body", true).append("in", BODY); 344 JsonMap schema = getSchema(param.getMap("schema"), type, bs); 345 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(schema, x)); 346 mpi.forEachAnnotation(Content.class, x -> true, x -> merge(schema, x.schema())); 347 pushupSchemaFields(BODY, param, schema); 348 param.appendIf(nem, "schema", schema); 349 param.putIfAbsent("required", true); 350 addBodyExamples(sm, param, false, type, locale); 351 352 } else if (mpi.hasAnnotation(Query.class) || pt.hasAnnotation(Query.class)) { 353 String name = QueryAnnotation.findName(mpi).orElse(null); 354 JsonMap param = paramMap.getMap(QUERY + "." + name, true).append("name", name).append("in", QUERY); 355 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(param, x)); 356 mpi.forEachAnnotation(Query.class, x -> true, x -> merge(param, x.schema())); 357 pushupSchemaFields(QUERY, param, getSchema(param.getMap("schema"), type, bs)); 358 addParamExample(sm, param, QUERY, type); 359 360 } else if (mpi.hasAnnotation(FormData.class) || pt.hasAnnotation(FormData.class)) { 361 String name = FormDataAnnotation.findName(mpi).orElse(null); 362 JsonMap param = paramMap.getMap(FORM_DATA + "." + name, true).append("name", name).append("in", FORM_DATA); 363 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(param, x)); 364 mpi.forEachAnnotation(FormData.class, x -> true, x -> merge(param, x.schema())); 365 pushupSchemaFields(FORM_DATA, param, getSchema(param.getMap("schema"), type, bs)); 366 addParamExample(sm, param, FORM_DATA, type); 367 368 } else if (mpi.hasAnnotation(Header.class) || pt.hasAnnotation(Header.class)) { 369 String name = HeaderAnnotation.findName(mpi).orElse(null); 370 JsonMap param = paramMap.getMap(HEADER + "." + name, true).append("name", name).append("in", HEADER); 371 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(param, x)); 372 mpi.forEachAnnotation(Header.class, x -> true, x -> merge(param, x.schema())); 373 pushupSchemaFields(HEADER, param, getSchema(param.getMap("schema"), type, bs)); 374 addParamExample(sm, param, HEADER, type); 375 376 } else if (mpi.hasAnnotation(Path.class) || pt.hasAnnotation(Path.class)) { 377 String name = PathAnnotation.findName(mpi).orElse(null); 378 JsonMap param = paramMap.getMap(PATH + "." + name, true).append("name", name).append("in", PATH); 379 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(param, x)); 380 mpi.forEachAnnotation(Path.class, x -> true, x -> merge(param, x.schema())); 381 pushupSchemaFields(PATH, param, getSchema(param.getMap("schema"), type, bs)); 382 addParamExample(sm, param, PATH, type); 383 param.putIfAbsent("required", true); 384 } 385 } 386 387 if (! paramMap.isEmpty()) 388 op.put("parameters", paramMap.values()); 389 390 JsonMap responses = op.getMap("responses", true); 391 392 for (ClassInfo eci : mi.getExceptionTypes()) { 393 if (eci.hasAnnotation(Response.class)) { 394 List<Response> la = eci.getAnnotations(context, Response.class); 395 List<StatusCode> la2 = eci.getAnnotations(context, StatusCode.class); 396 Set<Integer> codes = getCodes(la2, 500); 397 for (Response a : la) { 398 for (Integer code : codes) { 399 JsonMap om = responses.getMap(String.valueOf(code), true); 400 merge(om, a); 401 JsonMap schema = getSchema(om.getMap("schema"), m.getGenericReturnType(), bs); 402 eci.forEachAnnotation(Schema.class, x -> true, x -> merge(schema, x)); 403 pushupSchemaFields(RESPONSE, om, schema); 404 om.appendIf(nem, "schema", schema); 405 } 406 } 407 List<MethodInfo> methods = eci.getMethods(); 408 for (int i = methods.size()-1; i>=0; i--) { 409 MethodInfo ecmi = methods.get(i); 410 Header a = ecmi.getAnnotation(Header.class); 411 if (a == null) 412 a = ecmi.getReturnType().unwrap(Value.class,Optional.class).getAnnotation(Header.class); 413 if (a != null && ! isMulti(a)) { 414 String ha = a.name(); 415 for (Integer code : codes) { 416 JsonMap header = responses.getMap(String.valueOf(code), true).getMap("headers", true).getMap(ha, true); 417 ecmi.forEachAnnotation(context, Schema.class, x-> true, x -> merge(header, x)); 418 ecmi.getReturnType().unwrap(Value.class,Optional.class).forEachAnnotation(Schema.class, x -> true, x -> merge(header, x)); 419 pushupSchemaFields(RESPONSE_HEADER, header, getSchema(header.getMap("schema"), ecmi.getReturnType().unwrap(Value.class,Optional.class).innerType(), bs)); 420 } 421 } 422 } 423 } 424 } 425 426 if (mi.hasAnnotation(Response.class) || mi.getReturnType().unwrap(Value.class,Optional.class).hasAnnotation(Response.class)) { 427 List<Response> la = list(); 428 mi.forEachAnnotation(context, Response.class, x -> true, x -> la.add(x)); 429 List<StatusCode> la2 = list(); 430 mi.forEachAnnotation(context, StatusCode.class, x -> true, x -> la2.add(x)); 431 Set<Integer> codes = getCodes(la2, 200); 432 for (Response a : la) { 433 for (Integer code : codes) { 434 JsonMap om = responses.getMap(String.valueOf(code), true); 435 merge(om, a); 436 JsonMap schema = getSchema(om.getMap("schema"), m.getGenericReturnType(), bs); 437 mi.forEachAnnotation(context, Schema.class, x -> true, x -> merge(schema, x)); 438 pushupSchemaFields(RESPONSE, om, schema); 439 om.appendIf(nem, "schema", schema); 440 addBodyExamples(sm, om, true, m.getGenericReturnType(), locale); 441 } 442 } 443 if (mi.getReturnType().hasAnnotation(Response.class)) { 444 List<MethodInfo> methods = mi.getReturnType().getMethods(); 445 for (int i = methods.size()-1; i>=0; i--) { 446 MethodInfo ecmi = methods.get(i); 447 if (ecmi.hasAnnotation(Header.class)) { 448 Header a = ecmi.getAnnotation(Header.class); 449 String ha = a.name(); 450 if (! isMulti(a)) { 451 for (Integer code : codes) { 452 JsonMap header = responses.getMap(String.valueOf(code), true).getMap("headers", true).getMap(ha, true); 453 ecmi.forEachAnnotation(context, Schema.class, x -> true, x -> merge(header, x)); 454 ecmi.getReturnType().unwrap(Value.class,Optional.class).forEachAnnotation(Schema.class, x -> true, x -> merge(header, x)); 455 merge(header, a.schema()); 456 pushupSchemaFields(RESPONSE_HEADER, header, getSchema(header, ecmi.getReturnType().innerType(), bs)); 457 } 458 } 459 } 460 } 461 } 462 } else if (m.getGenericReturnType() != void.class) { 463 JsonMap om = responses.getMap("200", true); 464 ClassInfo pt2 = ClassInfo.of(m.getGenericReturnType()); 465 JsonMap schema = getSchema(om.getMap("schema"), m.getGenericReturnType(), bs); 466 pt2.forEachAnnotation(Schema.class, x -> true, x -> merge(schema, x)); 467 pushupSchemaFields(RESPONSE, om, schema); 468 om.appendIf(nem, "schema", schema); 469 addBodyExamples(sm, om, true, m.getGenericReturnType(), locale); 470 } 471 472 // Finally, look for Value @Header parameters defined on method. 473 for (ParamInfo mpi : mi.getParams()) { 474 475 ClassInfo pt = mpi.getParameterType(); 476 477 if (pt.is(Value.class) && (mpi.hasAnnotation(Header.class) || pt.hasAnnotation(Header.class))) { 478 List<Header> la = list(); 479 mpi.forEachAnnotation(Header.class, x -> true, x -> la.add(x)); 480 pt.forEachAnnotation(Header.class, x -> true, x -> la.add(x)); 481 List<StatusCode> la2 = list(); 482 mpi.forEachAnnotation(StatusCode.class, x -> true, x -> la2.add(x)); 483 pt.forEachAnnotation(StatusCode.class, x -> true, x -> la2.add(x)); 484 Set<Integer> codes = getCodes(la2, 200); 485 String name = HeaderAnnotation.findName(mpi).orElse(null); 486 Type type = Value.unwrap(mpi.getParameterType().innerType()); 487 for (Header a : la) { 488 if (! isMulti(a)) { 489 for (Integer code : codes) { 490 JsonMap header = responses.getMap(String.valueOf(code), true).getMap("headers", true).getMap(name, true); 491 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(header, x)); 492 merge(header, a.schema()); 493 pushupSchemaFields(RESPONSE_HEADER, header, getSchema(header, type, bs)); 494 } 495 } 496 } 497 498 } else if (mpi.hasAnnotation(Response.class) || pt.hasAnnotation(Response.class)) { 499 List<Response> la = list(); 500 mpi.forEachAnnotation(Response.class, x -> true, x -> la.add(x)); 501 pt.forEachAnnotation(Response.class, x -> true, x -> la.add(x)); 502 List<StatusCode> la2 = list(); 503 mpi.forEachAnnotation(StatusCode.class, x -> true, x -> la2.add(x)); 504 pt.forEachAnnotation(StatusCode.class, x -> true, x -> la2.add(x)); 505 Set<Integer> codes = getCodes(la2, 200); 506 Type type = Value.unwrap(mpi.getParameterType().innerType()); 507 for (Response a : la) { 508 for (Integer code : codes) { 509 JsonMap om = responses.getMap(String.valueOf(code), true); 510 merge(om, a); 511 JsonMap schema = getSchema(om.getMap("schema"), type, bs); 512 mpi.forEachAnnotation(Schema.class, x -> true, x -> merge(schema, x)); 513 la.forEach(x -> merge(schema, x.schema())); 514 pushupSchemaFields(RESPONSE, om, schema); 515 om.appendIf(nem, "schema", schema); 516 } 517 } 518 } 519 } 520 521 // Add default response descriptions. 522 for (Map.Entry<String,Object> e : responses.entrySet()) { 523 String key = e.getKey(); 524 JsonMap val = responses.getMap(key); 525 if (StringUtils.isDecimal(key)) 526 val.appendIfAbsentIf(ne, "description", RestUtils.getHttpResponseText(Integer.parseInt(key))); 527 } 528 529 if (responses.isEmpty()) 530 op.remove("responses"); 531 else 532 op.put("responses", new TreeMap<>(responses)); 533 534 if (! op.containsKey("consumes")) { 535 List<MediaType> mConsumes = sm.getSupportedContentTypes(); 536 if (! mConsumes.equals(consumes)) 537 op.put("consumes", mConsumes); 538 } 539 540 if (! op.containsKey("produces")) { 541 List<MediaType> mProduces = sm.getSupportedAcceptTypes(); 542 if (! mProduces.equals(produces)) 543 op.put("produces", mProduces); 544 } 545 } 546 547 if (js.getBeanDefs() != null) 548 for (Map.Entry<String,JsonMap> e : js.getBeanDefs().entrySet()) 549 definitions.put(e.getKey(), fixSwaggerExtensions(e.getValue())); 550 if (definitions.isEmpty()) 551 omSwagger.remove("definitions"); 552 553 if (! tagMap.isEmpty()) 554 omSwagger.put("tags", tagMap.values()); 555 556 if (consumes.isEmpty()) 557 omSwagger.remove("consumes"); 558 if (produces.isEmpty()) 559 omSwagger.remove("produces"); 560 561// try { 562// if (! omSwagger.isEmpty()) 563// assertNoEmpties(omSwagger); 564// } catch (SwaggerException e1) { 565// System.err.println(omSwagger.toString(Json5Serializer.DEFAULT_READABLE)); 566// throw e1; 567// } 568 569 try { 570 String swaggerJson = Json5Serializer.DEFAULT_READABLE.toString(omSwagger); 571// System.err.println(swaggerJson); 572 return jp.parse(swaggerJson, Swagger.class); 573 } catch (Exception e) { 574 throw new ServletException("Error detected in swagger.", e); 575 } 576 } 577 //================================================================================================================= 578 // Utility methods 579 //================================================================================================================= 580 581 private boolean isMulti(Header h) { 582 if ("*".equals(h.name()) || "*".equals(h.value())) 583 return true; 584 return false; 585 } 586 587 private JsonMap resolve(JsonMap om) throws ParseException { 588 JsonMap om2 = null; 589 if (om.containsKey("_value")) { 590 om = om.modifiable(); 591 om2 = parseMap(om.remove("_value")); 592 } else { 593 om2 = new JsonMap(); 594 } 595 for (Map.Entry<String,Object> e : om.entrySet()) { 596 Object val = e.getValue(); 597 if (val instanceof JsonMap) { 598 val = resolve((JsonMap)val); 599 } else if (val instanceof JsonList) { 600 val = resolve((JsonList) val); 601 } else if (val instanceof String) { 602 val = resolve(val.toString()); 603 } 604 om2.put(e.getKey(), val); 605 } 606 return om2; 607 } 608 609 private JsonList resolve(JsonList om) throws ParseException { 610 JsonList ol2 = new JsonList(); 611 for (Object val : om) { 612 if (val instanceof JsonMap) { 613 val = resolve((JsonMap)val); 614 } else if (val instanceof JsonList) { 615 val = resolve((JsonList) val); 616 } else if (val instanceof String) { 617 val = resolve(val.toString()); 618 } 619 ol2.add(val); 620 } 621 return ol2; 622 } 623 624 private String resolve(String[]...s) { 625 for (String[] ss : s) { 626 if (ss.length != 0) 627 return resolve(joinnl(ss)); 628 } 629 return null; 630 } 631 632 private String resolve(String s) { 633 if (s == null) 634 return null; 635 return vr.resolve(s.trim()); 636 } 637 638 private JsonMap parseMap(String[] o, String location, Object...args) throws ParseException { 639 if (o.length == 0) 640 return JsonMap.EMPTY_MAP; 641 try { 642 return parseMap(o); 643 } catch (ParseException e) { 644 throw new SwaggerException(e, "Malformed swagger JSON object encountered in " + location + ".", args); 645 } 646 } 647 648 private JsonMap parseMap(String o, String location, Object...args) throws ParseException { 649 try { 650 return parseMap(o); 651 } catch (ParseException e) { 652 throw new SwaggerException(e, "Malformed swagger JSON object encountered in " + location + ".", args); 653 } 654 } 655 656 private JsonMap parseMap(Object o) throws ParseException { 657 if (o == null) 658 return null; 659 if (o instanceof String[]) 660 o = joinnl((String[])o); 661 if (o instanceof String) { 662 String s = o.toString(); 663 if (s.isEmpty()) 664 return null; 665 s = resolve(s); 666 if ("IGNORE".equalsIgnoreCase(s)) 667 return JsonMap.of("ignore", true); 668 if (! isJsonObject(s, true)) 669 s = "{" + s + "}"; 670 return JsonMap.ofJson(s); 671 } 672 if (o instanceof JsonMap) 673 return (JsonMap)o; 674 throw new SwaggerException(null, "Unexpected data type ''{0}''. Expected JsonMap or String.", className(o)); 675 } 676 677 private JsonList parseList(Object o, String location, Object...locationArgs) throws ParseException { 678 try { 679 if (o == null) 680 return null; 681 String s = (o instanceof String[] ? joinnl((String[])o) : o.toString()); 682 if (s.isEmpty()) 683 return null; 684 s = resolve(s); 685 if (! isJsonArray(s, true)) 686 s = "[" + s + "]"; 687 return JsonList.ofJson(s); 688 } catch (ParseException e) { 689 throw new SwaggerException(e, "Malformed swagger JSON array encountered in "+location+".", locationArgs); 690 } 691 } 692 693 private JsonList parseListOrCdl(Object o, String location, Object...locationArgs) throws ParseException { 694 try { 695 if (o == null) 696 return null; 697 String s = (o instanceof String[] ? joinnl((String[])o) : o.toString()); 698 if (s.isEmpty()) 699 return null; 700 s = resolve(s); 701 return JsonList.ofJsonOrCdl(s); 702 } catch (ParseException e) { 703 throw new SwaggerException(e, "Malformed swagger JSON array encountered in "+location+".", locationArgs); 704 } 705 } 706 707 private JsonMap merge(JsonMap...maps) { 708 JsonMap m = maps[0]; 709 for (int i = 1; i < maps.length; i++) { 710 if (maps[i] != null) { 711 if (m == null) 712 m = new JsonMap(); 713 m.putAll(maps[i]); 714 } 715 } 716 return m; 717 } 718 719 private JsonList merge(JsonList...lists) { 720 JsonList l = lists[0]; 721 for (int i = 1; i < lists.length; i++) { 722 if (lists[i] != null) { 723 if (l == null) 724 l = new JsonList(); 725 l.addAll(lists[i]); 726 } 727 } 728 return l; 729 } 730 731 @SafeVarargs 732 private final <T> T firstNonEmpty(T...t) { 733 for (T oo : t) 734 if (! Utils.isEmpty(oo)) 735 return oo; 736 return null; 737 } 738 739 private JsonMap toMap(ExternalDocs a, String location, Object...locationArgs) { 740 if (ExternalDocsAnnotation.empty(a)) 741 return null; 742 Predicate<String> ne = Utils::isNotEmpty; 743 JsonMap om = JsonMap.create() 744 .appendIf(ne, "description", resolve(joinnl(a.description()))) 745 .appendIf(ne, "url", resolve(a.url())); 746 return nullIfEmpty(om); 747 } 748 749 private JsonMap toMap(Contact a, String location, Object...locationArgs) { 750 if (ContactAnnotation.empty(a)) 751 return null; 752 Predicate<String> ne = Utils::isNotEmpty; 753 JsonMap om = JsonMap.create() 754 .appendIf(ne, "name", resolve(a.name())) 755 .appendIf(ne, "url", resolve(a.url())) 756 .appendIf(ne, "email", resolve(a.email())); 757 return nullIfEmpty(om); 758 } 759 760 private JsonMap toMap(License a, String location, Object...locationArgs) { 761 if (LicenseAnnotation.empty(a)) 762 return null; 763 Predicate<String> ne = Utils::isNotEmpty; 764 JsonMap om = JsonMap.create() 765 .appendIf(ne, "name", resolve(a.name())) 766 .appendIf(ne, "url", resolve(a.url())); 767 return nullIfEmpty(om); 768 } 769 770 private JsonMap toMap(Tag a, String location, Object...locationArgs) { 771 JsonMap om = JsonMap.create(); 772 Predicate<String> ne = Utils::isNotEmpty; 773 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 774 om 775 .appendIf(ne, "name", resolve(a.name())) 776 .appendIf(ne, "description", resolve(joinnl(a.description()))) 777 .appendIf(nem, "externalDocs", merge(om.getMap("externalDocs"), toMap(a.externalDocs(), location, locationArgs))); 778 return nullIfEmpty(om); 779 } 780 781 private JsonList toList(Tag[] aa, String location, Object...locationArgs) { 782 if (aa.length == 0) 783 return null; 784 JsonList ol = new JsonList(); 785 for (Tag a : aa) 786 ol.add(toMap(a, location, locationArgs)); 787 return nullIfEmpty(ol); 788 } 789 790 private JsonMap getSchema(JsonMap schema, Type type, BeanSession bs) throws Exception { 791 792 if (type == Swagger.class) 793 return JsonMap.create(); 794 795 schema = newMap(schema); 796 797 ClassMeta<?> cm = bs.getClassMeta(type); 798 799 if (schema.getBoolean("ignore", false)) 800 return null; 801 802 if (schema.containsKey("type") || schema.containsKey("$ref")) 803 return schema; 804 805 JsonMap om = fixSwaggerExtensions(schema.append(js.getSchema(cm))); 806 807 return om; 808 } 809 810 /** 811 * Replaces non-standard JSON-Schema attributes with standard Swagger attributes. 812 */ 813 private JsonMap fixSwaggerExtensions(JsonMap om) { 814 Predicate<Object> nn = Utils::isNotNull; 815 om 816 .appendIf(nn, "discriminator", om.remove("x-discriminator")) 817 .appendIf(nn, "readOnly", om.remove("x-readOnly")) 818 .appendIf(nn, "xml", om.remove("x-xml")) 819 .appendIf(nn, "externalDocs", om.remove("x-externalDocs")) 820 .appendIf(nn, "example", om.remove("x-example")); 821 return nullIfEmpty(om); 822 } 823 824 private void addBodyExamples(RestOpContext sm, JsonMap piri, boolean response, Type type, Locale locale) throws Exception { 825 826 String sex = piri.getString("example"); 827 828 if (sex == null) { 829 JsonMap schema = resolveRef(piri.getMap("schema")); 830 if (schema != null) 831 sex = schema.getString("example", schema.getString("example")); 832 } 833 834 if (Utils.isEmpty(sex)) 835 return; 836 837 Object example = null; 838 if (isJson(sex)) { 839 example = jp.parse(sex, type); 840 } else { 841 ClassMeta<?> cm = js.getClassMeta(type); 842 if (cm.hasStringMutater()) { 843 example = cm.getStringMutater().mutate(sex); 844 } 845 } 846 847 String examplesKey = "examples"; // Parameters don't have an examples attribute. 848 849 JsonMap examples = piri.getMap(examplesKey); 850 if (examples == null) 851 examples = new JsonMap(); 852 853 List<MediaType> mediaTypes = response ? sm.getSerializers().getSupportedMediaTypes() : sm.getParsers().getSupportedMediaTypes(); 854 855 for (MediaType mt : mediaTypes) { 856 if (mt != MediaType.HTML) { 857 Serializer s2 = sm.getSerializers().getSerializer(mt); 858 if (s2 != null) { 859 try { 860 String eVal = s2 861 .createSession() 862 .locale(locale) 863 .mediaType(mt) 864 .apply(WriterSerializerSession.Builder.class, x -> x.useWhitespace(true)) 865 .build() 866 .serializeToString(example); 867 examples.put(s2.getPrimaryMediaType().toString(), eVal); 868 } catch (Exception e) { 869 System.err.println("Could not serialize to media type ["+mt+"]: " + e.getLocalizedMessage()); // NOT DEBUG 870 } 871 } 872 } 873 } 874 875 if (! examples.isEmpty()) 876 piri.put(examplesKey, examples); 877 } 878 879 private void addParamExample(RestOpContext sm, JsonMap piri, RestPartType in, Type type) throws Exception { 880 881 String s = piri.getString("example"); 882 883 if (Utils.isEmpty(s)) 884 return; 885 886 JsonMap examples = piri.getMap("examples"); 887 if (examples == null) 888 examples = new JsonMap(); 889 890 String paramName = piri.getString("name"); 891 892 if (in == QUERY) 893 s = "?" + urlEncodeLax(paramName) + "=" + urlEncodeLax(s); 894 else if (in == FORM_DATA) 895 s = paramName + "=" + s; 896 else if (in == HEADER) 897 s = paramName + ": " + s; 898 else if (in == PATH) 899 s = sm.getPathPattern().replace("{"+paramName+"}", urlEncodeLax(s)); 900 901 examples.put("example", s); 902 903 if (! examples.isEmpty()) 904 piri.put("examples", examples); 905 } 906 907 908 private JsonMap resolveRef(JsonMap m) { 909 if (m == null) 910 return null; 911 if (m.containsKey("$ref") && js.getBeanDefs() != null) { 912 String ref = m.getString("$ref"); 913 if (ref.startsWith("#/definitions/")) 914 return js.getBeanDefs().get(ref.substring(14)); 915 } 916 return m; 917 } 918 919 private JsonMap getOperation(JsonMap om, String path, String httpMethod) { 920 if (! om.containsKey("paths")) 921 om.put("paths", new JsonMap()); 922 om = om.getMap("paths"); 923 if (! om.containsKey(path)) 924 om.put(path, new JsonMap()); 925 om = om.getMap(path); 926 if (! om.containsKey(httpMethod)) 927 om.put(httpMethod, new JsonMap()); 928 return om.getMap(httpMethod); 929 } 930 931 private static JsonMap newMap(JsonMap om) { 932 if (om == null) 933 return new JsonMap(); 934 return om.modifiable(); 935 } 936 937 private JsonMap merge(JsonMap om, Schema a) { 938 try { 939 if (SchemaAnnotation.empty(a)) 940 return om; 941 om = newMap(om); 942 Predicate<String> ne = Utils::isNotEmpty; 943 Predicate<Collection<?>> nec = Utils::isNotEmpty; 944 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 945 Predicate<Boolean> nf = Utils::isTrue; 946 Predicate<Long> nm1 = Utils::isNotMinusOne; 947 return om 948 .appendIf(nem, "additionalProperties", toJsonMap(a.additionalProperties())) 949 .appendIf(ne, "allOf", joinnl(a.allOf())) 950 .appendFirst(ne, "collectionFormat", a.collectionFormat(), a.cf()) 951 .appendIf(ne, "default", joinnl(a._default(), a.df())) 952 .appendIf(ne, "discriminator", a.discriminator()) 953 .appendIf(ne, "description", resolve(a.description(), a.d())) 954 .appendFirst(nec, "enum", toSet(a._enum()), toSet(a.e())) 955 .appendIf(nf, "exclusiveMaximum", a.exclusiveMaximum() || a.emax()) 956 .appendIf(nf, "exclusiveMinimum", a.exclusiveMinimum() || a.emin()) 957 .appendIf(nem, "externalDocs", merge(om.getMap("externalDocs"), a.externalDocs())) 958 .appendFirst(ne, "format", a.format(), a.f()) 959 .appendIf(ne, "ignore", a.ignore() ? "true" : null) 960 .appendIf(nem, "items", merge(om.getMap("items"), a.items())) 961 .appendFirst(ne, "maximum", a.maximum(), a.max()) 962 .appendFirst(nm1, "maxItems", a.maxItems(), a.maxi()) 963 .appendFirst(nm1, "maxLength", a.maxLength(), a.maxl()) 964 .appendFirst(nm1, "maxProperties", a.maxProperties(), a.maxp()) 965 .appendFirst(ne, "minimum", a.minimum(), a.min()) 966 .appendFirst(nm1, "minItems", a.minItems(), a.mini()) 967 .appendFirst(nm1, "minLength", a.minLength(), a.minl()) 968 .appendFirst(nm1, "minProperties", a.minProperties(), a.minp()) 969 .appendFirst(ne, "multipleOf", a.multipleOf(), a.mo()) 970 .appendFirst(ne, "pattern", a.pattern(), a.p()) 971 .appendIf(nem, "properties", toJsonMap(a.properties())) 972 .appendIf(nf, "readOnly", a.readOnly() || a.ro()) 973 .appendIf(nf, "required", a.required() || a.r()) 974 .appendIf(ne, "title", a.title()) 975 .appendFirst(ne, "type", a.type(), a.t()) 976 .appendIf(nf, "uniqueItems", a.uniqueItems() || a.ui()) 977 .appendIf(ne, "xml", joinnl(a.xml())) 978 .appendIf(ne, "$ref", a.$ref()) 979 ; 980 } catch (ParseException e) { 981 throw new IllegalArgumentException(e); 982 } 983 } 984 985 private JsonMap merge(JsonMap om, ExternalDocs a) { 986 if (ExternalDocsAnnotation.empty(a)) 987 return om; 988 om = newMap(om); 989 Predicate<String> ne = Utils::isNotEmpty; 990 return om 991 .appendIf(ne, "description", resolve(a.description())) 992 .appendIf(ne, "url", a.url()) 993 ; 994 } 995 996 private JsonMap merge(JsonMap om, Items a) throws ParseException { 997 if (ItemsAnnotation.empty(a)) 998 return om; 999 om = newMap(om); 1000 Predicate<String> ne = Utils::isNotEmpty; 1001 Predicate<Collection<?>> nec = Utils::isNotEmpty; 1002 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 1003 Predicate<Boolean> nf = Utils::isTrue; 1004 Predicate<Long> nm1 = Utils::isNotMinusOne; 1005 return om 1006 .appendFirst(ne, "collectionFormat", a.collectionFormat(), a.cf()) 1007 .appendIf(ne, "default", joinnl(a._default(), a.df())) 1008 .appendFirst(nec, "enum", toSet(a._enum()), toSet(a.e())) 1009 .appendFirst(ne, "format", a.format(), a.f()) 1010 .appendIf(nf, "exclusiveMaximum", a.exclusiveMaximum() || a.emax()) 1011 .appendIf(nf, "exclusiveMinimum", a.exclusiveMinimum() || a.emin()) 1012 .appendIf(nem, "items", merge(om.getMap("items"), a.items())) 1013 .appendFirst(ne, "maximum", a.maximum(), a.max()) 1014 .appendFirst(nm1, "maxItems", a.maxItems(), a.maxi()) 1015 .appendFirst(nm1, "maxLength", a.maxLength(), a.maxl()) 1016 .appendFirst(ne, "minimum", a.minimum(), a.min()) 1017 .appendFirst(nm1, "minItems", a.minItems(), a.mini()) 1018 .appendFirst(nm1, "minLength", a.minLength(), a.minl()) 1019 .appendFirst(ne, "multipleOf", a.multipleOf(), a.mo()) 1020 .appendFirst(ne, "pattern", a.pattern(), a.p()) 1021 .appendIf(nf, "uniqueItems", a.uniqueItems() || a.ui()) 1022 .appendFirst(ne, "type", a.type(), a.t()) 1023 .appendIf(ne, "$ref", a.$ref()) 1024 ; 1025 } 1026 1027 private JsonMap merge(JsonMap om, SubItems a) throws ParseException { 1028 if (SubItemsAnnotation.empty(a)) 1029 return om; 1030 om = newMap(om); 1031 Predicate<String> ne = Utils::isNotEmpty; 1032 Predicate<Collection<?>> nec = Utils::isNotEmpty; 1033 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 1034 Predicate<Boolean> nf = Utils::isTrue; 1035 Predicate<Long> nm1 = Utils::isNotMinusOne; 1036 return om 1037 .appendFirst(ne, "collectionFormat", a.collectionFormat(), a.cf()) 1038 .appendIf(ne, "default", joinnl(a._default(), a.df())) 1039 .appendFirst(nec, "enum", toSet(a._enum()), toSet(a.e())) 1040 .appendIf(nf, "exclusiveMaximum", a.exclusiveMaximum() || a.emax()) 1041 .appendIf(nf, "exclusiveMinimum", a.exclusiveMinimum() || a.emin()) 1042 .appendFirst(ne, "format", a.format(), a.f()) 1043 .appendIf(nem, "items", toJsonMap(a.items())) 1044 .appendFirst(ne, "maximum", a.maximum(), a.max()) 1045 .appendFirst(nm1, "maxItems", a.maxItems(), a.maxi()) 1046 .appendFirst(nm1, "maxLength", a.maxLength(), a.maxl()) 1047 .appendFirst(ne, "minimum", a.minimum(), a.min()) 1048 .appendFirst(nm1, "minItems", a.minItems(), a.mini()) 1049 .appendFirst(nm1, "minLength", a.minLength(), a.minl()) 1050 .appendFirst(ne, "multipleOf", a.multipleOf(), a.mo()) 1051 .appendFirst(ne, "pattern", a.pattern(), a.p()) 1052 .appendFirst(ne, "type", a.type(), a.t()) 1053 .appendIf(nf, "uniqueItems", a.uniqueItems() || a.ui()) 1054 .appendIf(ne, "$ref", a.$ref()) 1055 ; 1056 } 1057 1058 private JsonMap merge(JsonMap om, Response a) throws ParseException { 1059 if (ResponseAnnotation.empty(a)) 1060 return om; 1061 om = newMap(om); 1062 Predicate<Map<?,?>> nem = Utils::isNotEmpty; 1063 if (! SchemaAnnotation.empty(a.schema())) 1064 merge(om, a.schema()); 1065 return om 1066 .appendIf(nem, "examples", parseMap(a.examples())) 1067 .appendIf(nem, "headers", merge(om.getMap("headers"), a.headers())) 1068 .appendIf(nem, "schema", merge(om.getMap("schema"), a.schema())) 1069 ; 1070 } 1071 1072 private JsonMap merge(JsonMap om, Header[] a) { 1073 if (a.length == 0) 1074 return om; 1075 om = newMap(om); 1076 for (Header aa : a) { 1077 String name = StringUtils.firstNonEmpty(aa.name(), aa.value()); 1078 if (Utils.isEmpty(name)) 1079 throw new IllegalArgumentException("@Header used without name or value."); 1080 merge(om.getMap(name, true), aa.schema()); 1081 } 1082 return om; 1083 } 1084 1085 private JsonMap pushupSchemaFields(RestPartType type, JsonMap param, JsonMap schema) { 1086 Predicate<Object> ne = Utils::isNotEmpty; 1087 if (schema != null && ! schema.isEmpty()) { 1088 if (type == BODY || type == RESPONSE) { 1089 param 1090 .appendIf(ne, "description", schema.remove("description")); 1091 } else { 1092 param 1093 .appendIfAbsentIf(ne, "collectionFormat", schema.remove("collectionFormat")) 1094 .appendIfAbsentIf(ne, "default", schema.remove("default")) 1095 .appendIfAbsentIf(ne, "description", schema.remove("description")) 1096 .appendIfAbsentIf(ne, "enum", schema.remove("enum")) 1097 .appendIfAbsentIf(ne, "example", schema.remove("example")) 1098 .appendIfAbsentIf(ne, "exclusiveMaximum", schema.remove("exclusiveMaximum")) 1099 .appendIfAbsentIf(ne, "exclusiveMinimum", schema.remove("exclusiveMinimum")) 1100 .appendIfAbsentIf(ne, "format", schema.remove("format")) 1101 .appendIfAbsentIf(ne, "items", schema.remove("items")) 1102 .appendIfAbsentIf(ne, "maximum", schema.remove("maximum")) 1103 .appendIfAbsentIf(ne, "maxItems", schema.remove("maxItems")) 1104 .appendIfAbsentIf(ne, "maxLength", schema.remove("maxLength")) 1105 .appendIfAbsentIf(ne, "minimum", schema.remove("minimum")) 1106 .appendIfAbsentIf(ne, "minItems", schema.remove("minItems")) 1107 .appendIfAbsentIf(ne, "minLength", schema.remove("minLength")) 1108 .appendIfAbsentIf(ne, "multipleOf", schema.remove("multipleOf")) 1109 .appendIfAbsentIf(ne, "pattern", schema.remove("pattern")) 1110 .appendIfAbsentIf(ne, "required", schema.remove("required")) 1111 .appendIfAbsentIf(ne, "type", schema.remove("type")) 1112 .appendIfAbsentIf(ne, "uniqueItems", schema.remove("uniqueItems")); 1113 1114 if ("object".equals(param.getString("type")) && ! schema.isEmpty()) 1115 param.put("schema", schema); 1116 } 1117 } 1118 1119 return param; 1120 } 1121 1122 private JsonMap toJsonMap(String[] ss) throws ParseException { 1123 if (ss.length == 0) 1124 return null; 1125 String s = joinnl(ss); 1126 if (s.isEmpty()) 1127 return null; 1128 if (! isJsonObject(s, true)) 1129 s = "{" + s + "}"; 1130 s = resolve(s); 1131 return JsonMap.ofJson(s); 1132 } 1133 1134 private Set<String> toSet(String[] ss) { 1135 if (ss.length == 0) 1136 return null; 1137 Set<String> set = set(); 1138 for (String s : ss) 1139 Utils.split(s, x -> set.add(x)); 1140 return set.isEmpty() ? null : set; 1141 } 1142 1143 static String joinnl(String[]...s) { 1144 for (String[] ss : s) { 1145 if (ss.length != 0) 1146 return Utils.joinnl(ss).trim(); 1147 } 1148 return ""; 1149 } 1150 1151 private static Set<Integer> getCodes(List<StatusCode> la, Integer def) { 1152 Set<Integer> codes = new TreeSet<>(); 1153 for (StatusCode a : la) { 1154 for (int i : a.value()) 1155 codes.add(i); 1156 } 1157 if (codes.isEmpty() && def != null) 1158 codes.add(def); 1159 return codes; 1160 } 1161 1162 private static JsonMap nullIfEmpty(JsonMap m) { 1163 return (m == null || m.isEmpty() ? null : m); 1164 } 1165 1166 private static JsonList nullIfEmpty(JsonList l) { 1167 return (l == null || l.isEmpty() ? null : l); 1168 } 1169}