View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.bean.openapi3;
18  
19  import static org.apache.juneau.commons.utils.AssertionUtils.*;
20  import static org.apache.juneau.commons.utils.CollectionUtils.*;
21  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
22  import static org.apache.juneau.commons.utils.Utils.*;
23  import static org.apache.juneau.internal.ConverterUtils.*;
24  
25  import java.util.*;
26  
27  import org.apache.juneau.commons.collections.*;
28  import org.apache.juneau.json.*;
29  import org.apache.juneau.objecttools.*;
30  
31  /**
32   * This is the root document object for the OpenAPI specification.
33   *
34   * <p>
35   * The OpenAPI Object is the root document that describes an entire API. It contains metadata about the API,
36   * available paths and operations, parameters, responses, authentication methods, and other information.
37   *
38   * <h5 class='section'>OpenAPI Specification:</h5>
39   * <p>
40   * The OpenAPI Object is composed of the following fields:
41   * <ul class='spaced-list'>
42   * 	<li><c>openapi</c> (string, REQUIRED) - The OpenAPI Specification version (e.g., "3.0.0")
43   * 	<li><c>info</c> ({@link Info}, REQUIRED) - Provides metadata about the API
44   * 	<li><c>servers</c> (array of {@link Server}) - An array of Server Objects providing connectivity information
45   * 	<li><c>paths</c> (map of {@link PathItem}) - The available paths and operations for the API
46   * 	<li><c>components</c> ({@link Components}) - An element to hold various schemas for reuse
47   * 	<li><c>security</c> (array of {@link SecurityRequirement}) - Security mechanisms applied to all operations
48   * 	<li><c>tags</c> (array of {@link Tag}) - A list of tags for API documentation control
49   * 	<li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation
50   * </ul>
51   *
52   * <h5 class='section'>Example:</h5>
53   * <p class='bjava'>
54   * 	<jc>// Create an OpenAPI document</jc>
55   * 	OpenApi <jv>doc</jv> = <jk>new</jk> OpenApi()
56   * 		.setOpenapi(<js>"3.0.0"</js>)
57   * 		.setInfo(
58   * 			<jk>new</jk> Info()
59   * 				.setTitle(<js>"My API"</js>)
60   * 				.setVersion(<js>"1.0.0"</js>)
61   * 		)
62   * 		.setPaths(
63   * 			JsonMap.<jsm>of</jsm>(
64   * 				<js>"/pets"</js>, <jk>new</jk> PathItem()
65   * 					.setGet(<jk>new</jk> Operation()
66   * 						.setSummary(<js>"List all pets"</js>)
67   * 					)
68   * 			)
69   * 		);
70   * </p>
71   *
72   * <h5 class='section'>See Also:</h5><ul>
73   * 	<li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#openapi-object">OpenAPI Specification &gt; OpenAPI Object</a>
74   * 	<li class='link'><a class="doclink" href="https://swagger.io/docs/specification/basic-structure/">OpenAPI Basic Structure</a>
75   * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
76   * </ul>
77   */
78  public class OpenApi extends OpenApiElement {
79  
80  	/** Represents a null OpenAPI document */
81  	public static final OpenApi NULL = new OpenApi();
82  
83  	private static final Comparator<String> PATH_COMPARATOR = (o1, o2) -> o1.replace('{', '@').compareTo(o2.replace('{', '@'));
84  
85  	private String openapi = "3.0.0";
86  	private Info info;
87  	private List<Server> servers = list();
88  	private Map<String,PathItem> paths;
89  	private Components components;
90  	private List<SecurityRequirement> security = list();
91  	private List<Tag> tags = list();
92  	private ExternalDocumentation externalDocs;
93  
94  	/**
95  	 * Default constructor.
96  	 */
97  	public OpenApi() {}
98  
99  	/**
100 	 * Copy constructor.
101 	 *
102 	 * @param copyFrom The object to copy.
103 	 */
104 	public OpenApi(OpenApi copyFrom) {
105 		super(copyFrom);
106 		this.openapi = copyFrom.openapi;
107 		this.info = copyFrom.info;
108 		if (nn(copyFrom.servers))
109 			this.servers.addAll(copyOf(copyFrom.servers, Server::copy));
110 		this.paths = copyOf(copyFrom.paths);
111 		this.components = copyFrom.components;
112 		if (nn(copyFrom.security))
113 			this.security.addAll(copyOf(copyFrom.security, SecurityRequirement::copy));
114 		if (nn(copyFrom.tags))
115 			this.tags.addAll(copyOf(copyFrom.tags, Tag::copy));
116 		this.externalDocs = copyFrom.externalDocs;
117 	}
118 
119 	/**
120 	 * Adds a path to this OpenAPI document.
121 	 *
122 	 * @param path The path string.  Must not be <jk>null</jk>.
123 	 * @param pathItem The path item.  Must not be <jk>null</jk>.
124 	 * @return This object.
125 	 */
126 	public OpenApi addPath(String path, PathItem pathItem) {
127 		assertArgNotNull("path", path);
128 		assertArgNotNull("pathItem", pathItem);
129 		if (paths == null)
130 			paths = new TreeMap<>(PATH_COMPARATOR);
131 		getPaths().put(path, pathItem);
132 		return this;
133 	}
134 
135 	/**
136 	 * Bean property fluent setter:  <property>security</property>.
137 	 *
138 	 * <p>
139 	 * A declaration of which security mechanisms can be used across the API.
140 	 *
141 	 * @param values
142 	 * 	The values to add to this property.
143 	 * 	<br>Ignored if <jk>null</jk>.
144 	 * @return This object.
145 	 */
146 	public OpenApi addSecurity(Collection<SecurityRequirement> values) {
147 		security = listb(SecurityRequirement.class).sparse().addAny(security, values).build();
148 		return this;
149 	}
150 
151 	/**
152 	 * Bean property fluent setter:  <property>security</property>.
153 	 *
154 	 * <p>
155 	 * A declaration of which security mechanisms can be used across the API.
156 	 *
157 	 * @param values
158 	 * 	The values to add to this property.
159 	 * 	<br>Ignored if <jk>null</jk>.
160 	 * @return This object.
161 	 */
162 	public OpenApi addSecurity(SecurityRequirement...values) {
163 		security = listb(SecurityRequirement.class).sparse().addAll(security).addAny((Object)values).build();
164 		return this;
165 	}
166 
167 	/**
168 	 * Bean property fluent setter:  <property>servers</property>.
169 	 *
170 	 * <p>
171 	 * An array of Server Objects, which provide connectivity information to a target server.
172 	 *
173 	 * @param values
174 	 * 	The values to add to this property.
175 	 * 	<br>Ignored if <jk>null</jk>.
176 	 * @return This object.
177 	 */
178 	public OpenApi addServers(Collection<Server> values) {
179 		if (nn(values))
180 			servers.addAll(values);
181 		return this;
182 	}
183 
184 	/**
185 	 * Bean property fluent setter:  <property>servers</property>.
186 	 *
187 	 * <p>
188 	 * An array of Server Objects, which provide connectivity information to a target server.
189 	 *
190 	 * @param values
191 	 * 	The values to add to this property.
192 	 * 	<br>Ignored if <jk>null</jk>.
193 	 * @return This object.
194 	 */
195 	public OpenApi addServers(Server...values) {
196 		if (nn(values))
197 			for (var v : values)
198 				if (nn(v))
199 					servers.add(v);
200 		return this;
201 	}
202 
203 	/**
204 	 * Bean property appender:  <property>tags</property>.
205 	 *
206 	 * <p>
207 	 * A list of tags used by the specification with additional metadata.
208 	 *
209 	 * @param values
210 	 * 	The values to add to this property.
211 	 * 	<br>Ignored if <jk>null</jk>.
212 	 * @return This object.
213 	 */
214 	public OpenApi addTags(Collection<Tag> values) {
215 		if (nn(values))
216 			tags.addAll(values);
217 		return this;
218 	}
219 
220 	/**
221 	 * Bean property appender:  <property>tags</property>.
222 	 *
223 	 * <p>
224 	 * A list of tags used by the specification with additional metadata.
225 	 *
226 	 * @param values
227 	 * 	The values to add to this property.
228 	 * 	<br>Ignored if <jk>null</jk>.
229 	 * @return This object.
230 	 */
231 	public OpenApi addTags(Tag...values) {
232 		if (nn(values))
233 			for (var v : values)
234 				if (nn(v))
235 					tags.add(v);
236 		return this;
237 	}
238 
239 	/**
240 	 * Make a deep copy of this object.
241 	 *
242 	 * @return A deep copy of this object.
243 	 */
244 	public OpenApi copy() {
245 		return new OpenApi(this);
246 	}
247 
248 	/**
249 	 * Finds a reference within this OpenAPI document.
250 	 *
251 	 * @param ref The reference string (e.g., <js>"#/components/schemas/User"</js>).  Must not be <jk>null</jk> or blank.
252 	 * @param c The expected class type.  Must not be <jk>null</jk>.
253 	 * @return The referenced node, or <jk>null</jk> if not found.
254 	 */
255 	public <T> T findRef(String ref, Class<T> c) {
256 		assertArgNotNullOrBlank("ref", ref);
257 		assertArgNotNull("c", c);
258 		if (! ref.startsWith("#/"))
259 			throw rex("Unsupported reference:  ''{0}''", ref);
260 		try {
261 			return new ObjectRest(this).get(ref.substring(1), c);
262 		} catch (Exception e) {
263 			throw bex(e, c, "Reference ''{0}'' could not be converted to type ''{1}''.", ref, cn(c));
264 		}
265 	}
266 
267 	@Override /* Overridden from OpenApiElement */
268 	public <T> T get(String property, Class<T> type) {
269 		assertArgNotNull("property", property);
270 		return switch (property) {
271 			case "openapi" -> toType(getOpenapi(), type);
272 			case "info" -> toType(getInfo(), type);
273 			case "servers" -> toType(getServers(), type);
274 			case "paths" -> toType(getPaths(), type);
275 			case "components" -> toType(getComponents(), type);
276 			case "security" -> toType(getSecurity(), type);
277 			case "tags" -> toType(getTags(), type);
278 			case "externalDocs" -> toType(getExternalDocs(), type);
279 			default -> super.get(property, type);
280 		};
281 	}
282 
283 	/**
284 	 * Returns the components object.
285 	 *
286 	 * @return The components object.
287 	 */
288 	public Components getComponents() { return components; }
289 
290 	/**
291 	 * Returns the external documentation.
292 	 *
293 	 * @return The external documentation.
294 	 */
295 	public ExternalDocumentation getExternalDocs() { return externalDocs; }
296 
297 	/**
298 	 * Returns the info object.
299 	 *
300 	 * @return The info object.
301 	 */
302 	public Info getInfo() { return info; }
303 
304 	/**
305 	 * Returns the OpenAPI version.
306 	 *
307 	 * @return The OpenAPI version.
308 	 */
309 	public String getOpenapi() { return openapi; }
310 
311 	/**
312 	 * Returns the paths map.
313 	 *
314 	 * @return The paths map.
315 	 */
316 	public Map<String,PathItem> getPaths() { return paths; }
317 
318 	/**
319 	 * Returns the security requirements list.
320 	 *
321 	 * @return The security requirements list.
322 	 */
323 	public List<SecurityRequirement> getSecurity() { return nullIfEmpty(security); }
324 
325 	/**
326 	 * Returns the servers list.
327 	 *
328 	 * @return The servers list.
329 	 */
330 	public List<Server> getServers() { return nullIfEmpty(servers); }
331 
332 	/**
333 	 * Returns the tags list.
334 	 *
335 	 * @return The tags list.
336 	 */
337 	public List<Tag> getTags() { return nullIfEmpty(tags); }
338 
339 	@Override /* Overridden from OpenApiElement */
340 	public Set<String> keySet() {
341 		// @formatter:off
342 		var s = setb(String.class)
343 			.addIf(nn(components), "components")
344 			.addIf(nn(externalDocs), "externalDocs")
345 			.addIf(nn(info), "info")
346 			.addIf(nn(openapi), "openapi")
347 			.addIf(nn(paths), "paths")
348 			.addIf(ne(security), "security")
349 			.addIf(ne(servers), "servers")
350 			.addIf(ne(tags), "tags")
351 			.build();
352 		// @formatter:on
353 		return new MultiSet<>(s, super.keySet());
354 	}
355 
356 	@Override /* Overridden from OpenApiElement */
357 	public OpenApi set(String property, Object value) {
358 		assertArgNotNull("property", property);
359 		return switch (property) {
360 			case "components" -> setComponents(toType(value, Components.class));
361 			case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class));
362 			case "info" -> setInfo(toType(value, Info.class));
363 			case "openapi" -> setOpenapi(s(value));
364 			case "paths" -> setPaths(toMapBuilder(value, String.class, PathItem.class).sparse().build());
365 			case "security" -> setSecurity(listb(SecurityRequirement.class).addAny(value).sparse().build());
366 			case "servers" -> setServers(listb(Server.class).addAny(value).sparse().build());
367 			case "tags" -> setTags(listb(Tag.class).addAny(value).sparse().build());
368 			default -> {
369 				super.set(property, value);
370 				yield this;
371 			}
372 		};
373 	}
374 
375 	/**
376 	 * Sets the components object.
377 	 *
378 	 * @param value The new value for this property.
379 	 * @return This object.
380 	 */
381 	public OpenApi setComponents(Components value) {
382 		components = value;
383 		return this;
384 	}
385 
386 	/**
387 	 * Sets the external documentation.
388 	 *
389 	 * @param value The new value for this property.
390 	 * @return This object.
391 	 */
392 	public OpenApi setExternalDocs(ExternalDocumentation value) {
393 		externalDocs = value;
394 		return this;
395 	}
396 
397 	/**
398 	 * Sets the info object.
399 	 *
400 	 * @param value The new value for this property.
401 	 * @return This object.
402 	 */
403 	public OpenApi setInfo(Info value) {
404 		info = value;
405 		return this;
406 	}
407 
408 	/**
409 	 * Sets the OpenAPI version.
410 	 *
411 	 * @param value The new value for this property.
412 	 * @return This object.
413 	 */
414 	public OpenApi setOpenapi(String value) {
415 		openapi = value;
416 		return this;
417 	}
418 
419 	/**
420 	 * Sets the paths map.
421 	 *
422 	 * @param value The new value for this property.
423 	 * @return This object.
424 	 */
425 	public OpenApi setPaths(Map<String,PathItem> value) {
426 		this.paths = toMapBuilder(value, String.class, PathItem.class).sparse().sorted(PATH_COMPARATOR).build();
427 		return this;
428 	}
429 
430 	/**
431 	 * Sets the security requirements list.
432 	 *
433 	 * @param value The new value for this property.
434 	 * @return This object.
435 	 */
436 	public OpenApi setSecurity(List<SecurityRequirement> value) {
437 		security.clear();
438 		if (nn(value))
439 			security.addAll(value);
440 		return this;
441 	}
442 
443 	/**
444 	 * Sets the servers list.
445 	 *
446 	 * @param value The new value for this property.
447 	 * @return This object.
448 	 */
449 	public OpenApi setServers(List<Server> value) {
450 		servers.clear();
451 		if (nn(value))
452 			servers.addAll(value);
453 		return this;
454 	}
455 
456 	/**
457 	 * Sets the tags list.
458 	 *
459 	 * @param value The new value for this property.
460 	 * @return This object.
461 	 */
462 	public OpenApi setTags(List<Tag> value) {
463 		tags.clear();
464 		if (nn(value))
465 			tags.addAll(value);
466 		return this;
467 	}
468 
469 	/**
470 	 * Bean property setter:  <property>tags</property>.
471 	 *
472 	 * <p>
473 	 * A list of tags used by the specification with additional metadata.
474 	 *
475 	 * @param value
476 	 * 	The new value for this property.
477 	 * 	<br>Ignored if <jk>null</jk>.
478 	 * @return This object.
479 	 */
480 	public OpenApi setTags(Tag...value) {
481 		setTags(listb(Tag.class).add(value).sparse().build());
482 		return this;
483 	}
484 
485 	@Override /* Overridden from OpenApiElement */
486 	public OpenApi strict() {
487 		super.strict();
488 		return this;
489 	}
490 
491 	@Override /* Overridden from OpenApiElement */
492 	public OpenApi strict(Object value) {
493 		super.strict(value);
494 		return this;
495 	}
496 
497 	@Override
498 	public String toString() {
499 		return JsonSerializer.DEFAULT.toString(this);
500 	}
501 }