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