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.bean.openapi3; 018 019import static org.apache.juneau.common.utils.Utils.*; 020import static org.apache.juneau.internal.ClassUtils.*; 021import static org.apache.juneau.internal.CollectionUtils.*; 022import static org.apache.juneau.internal.ConverterUtils.*; 023 024import java.util.*; 025 026import org.apache.juneau.*; 027import org.apache.juneau.common.utils.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.json.*; 030import org.apache.juneau.objecttools.*; 031 032/** 033 * This is the root document object for the OpenAPI specification. 034 * 035 * <p> 036 * The OpenAPI Object is the root document that describes an entire API. It contains metadata about the API, 037 * available paths and operations, parameters, responses, authentication methods, and other information. 038 * 039 * <h5 class='section'>OpenAPI Specification:</h5> 040 * <p> 041 * The OpenAPI Object is composed of the following fields: 042 * <ul class='spaced-list'> 043 * <li><c>openapi</c> (string, REQUIRED) - The OpenAPI Specification version (e.g., "3.0.0") 044 * <li><c>info</c> ({@link Info}, REQUIRED) - Provides metadata about the API 045 * <li><c>servers</c> (array of {@link Server}) - An array of Server Objects providing connectivity information 046 * <li><c>paths</c> (map of {@link PathItem}) - The available paths and operations for the API 047 * <li><c>components</c> ({@link Components}) - An element to hold various schemas for reuse 048 * <li><c>security</c> (array of {@link SecurityRequirement}) - Security mechanisms applied to all operations 049 * <li><c>tags</c> (array of {@link Tag}) - A list of tags for API documentation control 050 * <li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation 051 * </ul> 052 * 053 * <h5 class='section'>Example:</h5> 054 * <p class='bjava'> 055 * <jc>// Create an OpenAPI document</jc> 056 * OpenApi <jv>doc</jv> = <jk>new</jk> OpenApi() 057 * .setOpenapi(<js>"3.0.0"</js>) 058 * .setInfo( 059 * <jk>new</jk> Info() 060 * .setTitle(<js>"My API"</js>) 061 * .setVersion(<js>"1.0.0"</js>) 062 * ) 063 * .setPaths( 064 * JsonMap.<jsm>of</jsm>( 065 * <js>"/pets"</js>, <jk>new</jk> PathItem() 066 * .setGet(<jk>new</jk> Operation() 067 * .setSummary(<js>"List all pets"</js>) 068 * ) 069 * ) 070 * ); 071 * </p> 072 * 073 * <h5 class='section'>See Also:</h5><ul> 074 * <li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#openapi-object">OpenAPI Specification > OpenAPI Object</a> 075 * <li class='link'><a class="doclink" href="https://swagger.io/docs/specification/basic-structure/">OpenAPI Basic Structure</a> 076 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a> 077 * </ul> 078 */ 079public class OpenApi extends OpenApiElement { 080 081 /** Represents a null OpenAPI document */ 082 public static final OpenApi NULL = new OpenApi(); 083 084 private static final Comparator<String> PATH_COMPARATOR = (o1, o2) -> o1.replace('{', '@').compareTo(o2.replace('{', '@')); 085 086 private String openapi = "3.0.0"; 087 private Info info; 088 private List<Server> servers; 089 private Map<String,PathItem> paths; 090 private Components components; 091 private List<SecurityRequirement> security; 092 private List<Tag> tags; 093 private ExternalDocumentation externalDocs; 094 095 /** 096 * Default constructor. 097 */ 098 public OpenApi() {} 099 100 /** 101 * Copy constructor. 102 * 103 * @param copyFrom The object to copy. 104 */ 105 public OpenApi(OpenApi copyFrom) { 106 super(copyFrom); 107 this.openapi = copyFrom.openapi; 108 this.info = copyFrom.info; 109 this.servers = copyOf(copyFrom.servers); 110 this.paths = copyOf(copyFrom.paths); 111 this.components = copyFrom.components; 112 this.security = copyOf(copyFrom.security); 113 this.tags = copyOf(copyFrom.tags); 114 this.externalDocs = copyFrom.externalDocs; 115 } 116 117 /** 118 * Make a deep copy of this object. 119 * 120 * @return A deep copy of this object. 121 */ 122 public OpenApi copy() { 123 return new OpenApi(this); 124 } 125 126 /** 127 * Returns the OpenAPI version. 128 * 129 * @return The OpenAPI version. 130 */ 131 public String getOpenapi() { 132 return openapi; 133 } 134 135 /** 136 * Sets the OpenAPI version. 137 * 138 * @param value The new value for this property. 139 * @return This object. 140 */ 141 public OpenApi setOpenapi(String value) { 142 this.openapi = value; 143 return this; 144 } 145 146 /** 147 * Returns the info object. 148 * 149 * @return The info object. 150 */ 151 public Info getInfo() { 152 return info; 153 } 154 155 /** 156 * Sets the info object. 157 * 158 * @param value The new value for this property. 159 * @return This object. 160 */ 161 public OpenApi setInfo(Info value) { 162 this.info = value; 163 return this; 164 } 165 166 /** 167 * Returns the servers list. 168 * 169 * @return The servers list. 170 */ 171 public List<Server> getServers() { 172 return servers; 173 } 174 175 /** 176 * Sets the servers list. 177 * 178 * @param value The new value for this property. 179 * @return This object. 180 */ 181 public OpenApi setServers(List<Server> value) { 182 this.servers = value; 183 return this; 184 } 185 186 /** 187 * Returns the paths map. 188 * 189 * @return The paths map. 190 */ 191 public Map<String,PathItem> getPaths() { 192 return paths; 193 } 194 195 /** 196 * Sets the paths map. 197 * 198 * @param value The new value for this property. 199 * @return This object. 200 */ 201 public OpenApi setPaths(Map<String,PathItem> value) { 202 this.paths = mapBuilder(String.class,PathItem.class).sparse().sorted(PATH_COMPARATOR).addAll(value).build(); 203 return this; 204 } 205 206 /** 207 * Adds a path to this OpenAPI document. 208 * 209 * @param path The path string. Must not be <jk>null</jk>. 210 * @param pathItem The path item. Must not be <jk>null</jk>. 211 * @return This object. 212 */ 213 public OpenApi addPath(String path, PathItem pathItem) { 214 assertArgNotNull("path", path); 215 assertArgNotNull("pathItem", pathItem); 216 if (paths == null) 217 paths = new TreeMap<>(PATH_COMPARATOR); 218 getPaths().put(path, pathItem); 219 return this; 220 } 221 222 /** 223 * Returns the components object. 224 * 225 * @return The components object. 226 */ 227 public Components getComponents() { 228 return components; 229 } 230 231 /** 232 * Sets the components object. 233 * 234 * @param value The new value for this property. 235 * @return This object. 236 */ 237 public OpenApi setComponents(Components value) { 238 this.components = value; 239 return this; 240 } 241 242 /** 243 * Returns the security requirements list. 244 * 245 * @return The security requirements list. 246 */ 247 public List<SecurityRequirement> getSecurity() { 248 return security; 249 } 250 251 /** 252 * Sets the security requirements list. 253 * 254 * @param value The new value for this property. 255 * @return This object. 256 */ 257 public OpenApi setSecurity(List<SecurityRequirement> value) { 258 this.security = value; 259 return this; 260 } 261 262 /** 263 * Returns the tags list. 264 * 265 * @return The tags list. 266 */ 267 public List<Tag> getTags() { 268 return tags; 269 } 270 271 /** 272 * Sets the tags list. 273 * 274 * @param value The new value for this property. 275 * @return This object. 276 */ 277 public OpenApi setTags(List<Tag> value) { 278 this.tags = value; 279 return this; 280 } 281 282 /** 283 * Bean property setter: <property>tags</property>. 284 * 285 * <p> 286 * A list of tags used by the specification with additional metadata. 287 * 288 * @param value 289 * The new value for this property. 290 * <br>Ignored if <jk>null</jk>. 291 * @return This object. 292 */ 293 public OpenApi setTags(Tag...value) { 294 setTags(listBuilder(Tag.class).sparse().add(value).build()); 295 return this; 296 } 297 298 /** 299 * Bean property appender: <property>tags</property>. 300 * 301 * <p> 302 * A list of tags used by the specification with additional metadata. 303 * 304 * @param values 305 * The values to add to this property. 306 * <br>Ignored if <jk>null</jk>. 307 * @return This object. 308 */ 309 public OpenApi addTags(Tag...values) { 310 tags = listBuilder(tags).sparse().add(values).build(); 311 return this; 312 } 313 314 /** 315 * Bean property appender: <property>tags</property>. 316 * 317 * <p> 318 * A list of tags used by the specification with additional metadata. 319 * 320 * @param values 321 * The values to add to this property. 322 * <br>Ignored if <jk>null</jk>. 323 * @return This object. 324 */ 325 public OpenApi addTags(Collection<Tag> values) { 326 tags = listBuilder(tags).sparse().addAll(values).build(); 327 return this; 328 } 329 330 /** 331 * Bean property fluent setter: <property>servers</property>. 332 * 333 * <p> 334 * An array of Server Objects, which provide connectivity information to a target server. 335 * 336 * @param values 337 * The values to add to this property. 338 * <br>Ignored if <jk>null</jk>. 339 * @return This object. 340 */ 341 public OpenApi addServers(Server...values) { 342 servers = listBuilder(servers).sparse().add(values).build(); 343 return this; 344 } 345 346 /** 347 * Bean property fluent setter: <property>servers</property>. 348 * 349 * <p> 350 * An array of Server Objects, which provide connectivity information to a target server. 351 * 352 * @param values 353 * The values to add to this property. 354 * <br>Ignored if <jk>null</jk>. 355 * @return This object. 356 */ 357 public OpenApi addServers(Collection<Server> values) { 358 servers = listBuilder(servers).sparse().addAll(values).build(); 359 return this; 360 } 361 362 /** 363 * Bean property fluent setter: <property>security</property>. 364 * 365 * <p> 366 * A declaration of which security mechanisms can be used across the API. 367 * 368 * @param values 369 * The values to add to this property. 370 * <br>Ignored if <jk>null</jk>. 371 * @return This object. 372 */ 373 public OpenApi addSecurity(SecurityRequirement...values) { 374 security = listBuilder(security).sparse().add(values).build(); 375 return this; 376 } 377 378 /** 379 * Bean property fluent setter: <property>security</property>. 380 * 381 * <p> 382 * A declaration of which security mechanisms can be used across the API. 383 * 384 * @param values 385 * The values to add to this property. 386 * <br>Ignored if <jk>null</jk>. 387 * @return This object. 388 */ 389 public OpenApi addSecurity(Collection<SecurityRequirement> values) { 390 security = listBuilder(security).sparse().addAll(values).build(); 391 return this; 392 } 393 394 /** 395 * Returns the external documentation. 396 * 397 * @return The external documentation. 398 */ 399 public ExternalDocumentation getExternalDocs() { 400 return externalDocs; 401 } 402 403 /** 404 * Sets the external documentation. 405 * 406 * @param value The new value for this property. 407 * @return This object. 408 */ 409 public OpenApi setExternalDocs(ExternalDocumentation value) { 410 this.externalDocs = value; 411 return this; 412 } 413 414 /** 415 * Finds a reference within this OpenAPI document. 416 * 417 * @param ref The reference string (e.g., <js>"#/components/schemas/User"</js>). Must not be <jk>null</jk> or blank. 418 * @param c The expected class type. Must not be <jk>null</jk>. 419 * @return The referenced node, or <jk>null</jk> if not found. 420 */ 421 public <T> T findRef(String ref, Class<T> c) { 422 assertArgNotNullOrBlank("ref", ref); 423 assertArgNotNull("c", c); 424 if (! ref.startsWith("#/")) 425 throw new BasicRuntimeException("Unsupported reference: ''{0}''", ref); 426 try { 427 return new ObjectRest(this).get(ref.substring(1), c); 428 } catch (Exception e) { 429 throw new BeanRuntimeException(e, c, "Reference ''{0}'' could not be converted to type ''{1}''.", ref, className(c)); 430 } 431 } 432 433 @Override 434 public String toString() { 435 return JsonSerializer.DEFAULT.toString(this); 436 } 437 438 @Override /* Overridden from OpenApiElement */ 439 public <T> T get(String property, Class<T> type) { 440 assertArgNotNull("property", property); 441 return switch (property) { 442 case "openapi" -> toType(getOpenapi(), type); 443 case "info" -> toType(getInfo(), type); 444 case "servers" -> toType(getServers(), type); 445 case "paths" -> toType(getPaths(), type); 446 case "components" -> toType(getComponents(), type); 447 case "security" -> toType(getSecurity(), type); 448 case "tags" -> toType(getTags(), type); 449 case "externalDocs" -> toType(getExternalDocs(), type); 450 default -> super.get(property, type); 451 }; 452 } 453 454 @Override /* Overridden from OpenApiElement */ 455 public OpenApi set(String property, Object value) { 456 assertArgNotNull("property", property); 457 return switch (property) { 458 case "components" -> setComponents(toType(value, Components.class)); 459 case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class)); 460 case "info" -> setInfo(toType(value, Info.class)); 461 case "openapi" -> setOpenapi(Utils.s(value)); 462 case "paths" -> setPaths(mapBuilder(String.class, PathItem.class).sparse().addAny(value).build()); 463 case "security" -> setSecurity(listBuilder(SecurityRequirement.class).sparse().addAny(value).build()); 464 case "servers" -> setServers(listBuilder(Server.class).sparse().addAny(value).build()); 465 case "tags" -> setTags(listBuilder(Tag.class).sparse().addAny(value).build()); 466 default -> { 467 super.set(property, value); 468 yield this; 469 } 470 }; 471 } 472 473 @Override /* Overridden from OpenApiElement */ 474 public Set<String> keySet() { 475 var s = setBuilder(String.class) 476 .addIf(components != null, "components") 477 .addIf(externalDocs != null, "externalDocs") 478 .addIf(info != null, "info") 479 .addIf(openapi != null, "openapi") 480 .addIf(paths != null, "paths") 481 .addIf(security != null, "security") 482 .addIf(servers != null, "servers") 483 .addIf(tags != null, "tags") 484 .build(); 485 return new MultiSet<>(s, super.keySet()); 486 } 487 488 @Override /* Overridden from OpenApiElement */ 489 public OpenApi strict() { 490 super.strict(); 491 return this; 492 } 493 494 @Override /* Overridden from OpenApiElement */ 495 public OpenApi strict(Object value) { 496 super.strict(value); 497 return this; 498 } 499 500}