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.Utils.*;
22  import static org.apache.juneau.internal.ConverterUtils.*;
23  
24  import java.util.*;
25  
26  import org.apache.juneau.commons.collections.*;
27  
28  /**
29   * Describes a single API operation on a path.
30   *
31   * <p>
32   * The Operation Object describes a single operation (such as GET, POST, PUT, DELETE) that can be performed on a path.
33   * Operations are the core of the API specification, defining what actions can be taken, what parameters they accept,
34   * and what responses they return.
35   *
36   * <h5 class='section'>OpenAPI Specification:</h5>
37   * <p>
38   * The Operation Object is composed of the following fields:
39   * <ul class='spaced-list'>
40   * 	<li><c>tags</c> (array of string) - A list of tags for API documentation control
41   * 	<li><c>summary</c> (string) - A short summary of what the operation does
42   * 	<li><c>description</c> (string) - A verbose explanation of the operation behavior (CommonMark syntax may be used)
43   * 	<li><c>externalDocs</c> ({@link ExternalDocumentation}) - Additional external documentation for this operation
44   * 	<li><c>operationId</c> (string) - Unique string used to identify the operation
45   * 	<li><c>parameters</c> (array of {@link Parameter}) - A list of parameters that are applicable for this operation
46   * 	<li><c>requestBody</c> ({@link RequestBodyInfo}) - The request body applicable for this operation
47   * 	<li><c>responses</c> (map of {@link Response}, REQUIRED) - The list of possible responses as they are returned from executing this operation
48   * 	<li><c>callbacks</c> (map of {@link Callback}) - A map of possible out-of band callbacks related to the parent operation
49   * 	<li><c>deprecated</c> (boolean) - Declares this operation to be deprecated
50   * 	<li><c>security</c> (array of {@link SecurityRequirement}) - A declaration of which security mechanisms can be used for this operation
51   * 	<li><c>servers</c> (array of {@link Server}) - An alternative server array to service this operation
52   * </ul>
53   *
54   * <h5 class='section'>Example:</h5>
55   * <p class='bjava'>
56   * 	<jc>// Create an Operation object for a GET endpoint</jc>
57   * 	Operation <jv>operation</jv> = <jk>new</jk> Operation()
58   * 		.setTags(<js>"pet"</js>)
59   * 		.setSummary(<js>"Find pets by status"</js>)
60   * 		.setDescription(<js>"Multiple status values can be provided with comma separated strings"</js>)
61   * 		.setOperationId(<js>"findPetsByStatus"</js>)
62   * 		.setParameters(
63   * 			<jk>new</jk> Parameter()
64   * 				.setName(<js>"status"</js>)
65   * 				.setIn(<js>"query"</js>)
66   * 				.setDescription(<js>"Status values that need to be considered for filter"</js>)
67   * 				.setRequired(<jk>true</jk>)
68   * 				.setSchema(
69   * 					<jk>new</jk> SchemaInfo().setType(<js>"string"</js>)
70   * 				)
71   * 		)
72   * 		.setResponses(
73   * 			JsonMap.<jsm>of</jsm>(
74   * 				<js>"200"</js>, <jk>new</jk> Response()
75   * 					.setDescription(<js>"successful operation"</js>)
76   * 					.setContent(
77   * 						JsonMap.<jsm>of</jsm>(
78   * 							<js>"application/json"</js>, <jk>new</jk> MediaType()
79   * 								.setSchema(<jk>new</jk> SchemaInfo().<jsm>setType</jsm>(<js>"array"</js>))
80   * 						)
81   * 					)
82   * 			)
83   * 		);
84   * </p>
85   *
86   * <h5 class='section'>See Also:</h5><ul>
87   * 	<li class='link'><a class="doclink" href="https://spec.openapis.org/oas/v3.0.0#operation-object">OpenAPI Specification &gt; Operation Object</a>
88   * 	<li class='link'><a class="doclink" href="https://swagger.io/docs/specification/paths-and-operations/">OpenAPI Paths and Operations</a>
89   * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanOpenApi3">juneau-bean-openapi-v3</a>
90   * </ul>
91   */
92  public class Operation extends OpenApiElement {
93  
94  	private List<String> tags = list();
95  	private String summary, description, operationId;
96  	private ExternalDocumentation externalDocs;
97  	private List<Parameter> parameters = list();
98  	private RequestBodyInfo requestBody;
99  	private Map<String,Response> responses = map();
100 	private Map<String,Callback> callbacks = map();
101 	private Boolean deprecated;
102 	private List<SecurityRequirement> security = list();
103 	private List<Server> servers = list();
104 
105 	/**
106 	 * Default constructor.
107 	 */
108 	public Operation() {}
109 
110 	/**
111 	 * Copy constructor.
112 	 *
113 	 * @param copyFrom The object to copy.
114 	 */
115 	public Operation(Operation copyFrom) {
116 		super(copyFrom);
117 		if (nn(copyFrom.tags))
118 			tags.addAll(copyFrom.tags);
119 		this.summary = copyFrom.summary;
120 		this.description = copyFrom.description;
121 		this.operationId = copyFrom.operationId;
122 		this.externalDocs = copyFrom.externalDocs;
123 		if (nn(copyFrom.parameters))
124 			parameters.addAll(copyOf(copyFrom.parameters, Parameter::copy));
125 		this.requestBody = copyFrom.requestBody;
126 		if (nn(copyFrom.responses))
127 			responses.putAll(copyFrom.responses);
128 		if (nn(copyFrom.callbacks))
129 			callbacks.putAll(copyFrom.callbacks);
130 		this.deprecated = copyFrom.deprecated;
131 		if (nn(copyFrom.security))
132 			security.addAll(copyOf(copyFrom.security, SecurityRequirement::copy));
133 		if (nn(copyFrom.servers))
134 			servers.addAll(copyOf(copyFrom.servers, Server::copy));
135 	}
136 
137 	/**
138 	 * Bean property fluent setter:  <property>callbacks</property>.
139 	 *
140 	 * <p>
141 	 * A map of possible out-of band callbacks related to the parent operation.
142 	 *
143 	 * @param name
144 	 * 	The name of the callback.
145 	 * 	<br>Must not be <jk>null</jk>.
146 	 * @param callback
147 	 * 	The callback object.
148 	 * 	<br>Must not be <jk>null</jk>.
149 	 * @return This object.
150 	 */
151 	public Operation addCallback(String name, Callback callback) {
152 		assertArgNotNull("name", name);
153 		assertArgNotNull("callback", callback);
154 		callbacks.put(name, callback);
155 		return this;
156 	}
157 
158 	/**
159 	 * Bean property fluent setter:  <property>parameters</property>.
160 	 *
161 	 * <p>
162 	 * A list of parameters that are applicable for this operation.
163 	 *
164 	 * @param values
165 	 * 	The values to add to this property.
166 	 * 	<br>Ignored if <jk>null</jk>.
167 	 * @return This object.
168 	 */
169 	public Operation addParameters(Collection<Parameter> values) {
170 		if (nn(values))
171 			parameters.addAll(values);
172 		return this;
173 	}
174 
175 	/**
176 	 * Bean property fluent setter:  <property>parameters</property>.
177 	 *
178 	 * <p>
179 	 * A list of parameters that are applicable for this operation.
180 	 *
181 	 * @param values
182 	 * 	The values to add to this property.
183 	 * 	<br>Ignored if <jk>null</jk>.
184 	 * @return This object.
185 	 */
186 	public Operation addParameters(Parameter...values) {
187 		if (nn(values))
188 			for (var v : values)
189 				if (nn(v))
190 					parameters.add(v);
191 		return this;
192 	}
193 
194 	/**
195 	 * Bean property fluent setter:  <property>responses</property>.
196 	 *
197 	 * <p>
198 	 * The list of possible responses as they are returned from executing this operation.
199 	 *
200 	 * @param statusCode
201 	 * 	The status code for the response.
202 	 * 	<br>Must not be <jk>null</jk>.
203 	 * @param response
204 	 * 	The response object.
205 	 * 	<br>Must not be <jk>null</jk>.
206 	 * @return This object.
207 	 */
208 	public Operation addResponse(String statusCode, Response response) {
209 		assertArgNotNull("statusCode", statusCode);
210 		assertArgNotNull("response", response);
211 		responses.put(statusCode, response);
212 		return this;
213 	}
214 
215 	/**
216 	 * Bean property fluent setter:  <property>security</property>.
217 	 *
218 	 * <p>
219 	 * A declaration of which security mechanisms can be used for this operation.
220 	 *
221 	 * @param values
222 	 * 	The values to add to this property.
223 	 * 	<br>Ignored if <jk>null</jk>.
224 	 * @return This object.
225 	 */
226 	public Operation addSecurity(Collection<SecurityRequirement> values) {
227 		if (nn(values))
228 			security.addAll(values);
229 		return this;
230 	}
231 
232 	/**
233 	 * Bean property fluent setter:  <property>security</property>.
234 	 *
235 	 * <p>
236 	 * A declaration of which security mechanisms can be used for this operation.
237 	 *
238 	 * @param values
239 	 * 	The values to add to this property.
240 	 * 	<br>Ignored if <jk>null</jk>.
241 	 * @return This object.
242 	 */
243 	public Operation addSecurity(SecurityRequirement...values) {
244 		if (nn(values))
245 			for (var v : values)
246 				if (nn(v))
247 					security.add(v);
248 		return this;
249 	}
250 
251 	/**
252 	 * Bean property fluent setter:  <property>servers</property>.
253 	 *
254 	 * <p>
255 	 * An alternative server array to service this operation.
256 	 *
257 	 * @param values
258 	 * 	The values to add to this property.
259 	 * 	<br>Ignored if <jk>null</jk>.
260 	 * @return This object.
261 	 */
262 	public Operation addServers(Collection<Server> values) {
263 		if (nn(values))
264 			servers.addAll(values);
265 		return this;
266 	}
267 
268 	/**
269 	 * Bean property fluent setter:  <property>servers</property>.
270 	 *
271 	 * <p>
272 	 * An alternative server array to service this operation.
273 	 *
274 	 * @param values
275 	 * 	The values to add to this property.
276 	 * 	<br>Ignored if <jk>null</jk>.
277 	 * @return This object.
278 	 */
279 	public Operation addServers(Server...values) {
280 		if (nn(values))
281 			for (var v : values)
282 				if (nn(v))
283 					servers.add(v);
284 		return this;
285 	}
286 
287 	/**
288 	 * Bean property appender:  <property>tags</property>.
289 	 *
290 	 * <p>
291 	 * A list of tags for API documentation control.
292 	 *
293 	 * @param values
294 	 * 	The values to add to this property.
295 	 * 	<br>Ignored if <jk>null</jk>.
296 	 * @return This object.
297 	 */
298 	public Operation addTags(Collection<String> values) {
299 		if (nn(values))
300 			tags.addAll(values);
301 		return this;
302 	}
303 
304 	/**
305 	 * Bean property appender:  <property>tags</property>.
306 	 *
307 	 * <p>
308 	 * A list of tags for API documentation control.
309 	 *
310 	 * @param values
311 	 * 	The values to add to this property.
312 	 * 	<br>Ignored if <jk>null</jk>.
313 	 * @return This object.
314 	 */
315 	public Operation addTags(String...values) {
316 		if (nn(values))
317 			for (var v : values)
318 				if (nn(v))
319 					tags.add(v);
320 		return this;
321 	}
322 
323 	/**
324 	 * Creates a copy of this object.
325 	 *
326 	 * @return A copy of this object.
327 	 */
328 	public Operation copy() {
329 		return new Operation(this);
330 	}
331 
332 	@Override /* Overridden from OpenApiElement */
333 	public <T> T get(String property, Class<T> type) {
334 		assertArgNotNull("property", property);
335 		return switch (property) {
336 			case "tags" -> toType(getTags(), type);
337 			case "summary" -> toType(getSummary(), type);
338 			case "description" -> toType(getDescription(), type);
339 			case "operationId" -> toType(getOperationId(), type);
340 			case "externalDocs" -> toType(getExternalDocs(), type);
341 			case "parameters" -> toType(getParameters(), type);
342 			case "requestBody" -> toType(getRequestBody(), type);
343 			case "responses" -> toType(getResponses(), type);
344 			case "callbacks" -> toType(getCallbacks(), type);
345 			case "deprecated" -> toType(getDeprecated(), type);
346 			case "security" -> toType(getSecurity(), type);
347 			case "servers" -> toType(getServers(), type);
348 			default -> super.get(property, type);
349 		};
350 	}
351 
352 	/**
353 	 * Returns the callbacks map.
354 	 *
355 	 * @return The callbacks map.
356 	 */
357 	public Map<String,Callback> getCallbacks() { return nullIfEmpty(callbacks); }
358 
359 	/**
360 	 * Returns the deprecated flag.
361 	 *
362 	 * @return The deprecated flag.
363 	 */
364 	public Boolean getDeprecated() { return deprecated; }
365 
366 	/**
367 	 * Returns the description.
368 	 *
369 	 * @return The description.
370 	 */
371 	public String getDescription() { return description; }
372 
373 	/**
374 	 * Returns the external documentation.
375 	 *
376 	 * @return The external documentation.
377 	 */
378 	public ExternalDocumentation getExternalDocs() { return externalDocs; }
379 
380 	/**
381 	 * Returns the operation ID.
382 	 *
383 	 * @return The operation ID.
384 	 */
385 	public String getOperationId() { return operationId; }
386 
387 	/**
388 	 * Returns the parameter with the specified type and name.
389 	 *
390 	 * @param in The parameter in.  Must not be <jk>null</jk>.
391 	 * @param name The parameter name.  Must not be <jk>null</jk>.
392 	 * @return The matching parameter, or <jk>null</jk> if not found.
393 	 */
394 	public Parameter getParameter(String in, String name) {
395 		assertArgNotNull("in", in);
396 		assertArgNotNull("name", name);
397 		for (var p : parameters)
398 			if (eq(p.getIn(), in) && eq(p.getName(), name))
399 				return p;
400 		return null;
401 	}
402 
403 	/**
404 	 * Returns the parameters list.
405 	 *
406 	 * @return The parameters list.
407 	 */
408 	public List<Parameter> getParameters() { return nullIfEmpty(parameters); }
409 
410 	/**
411 	 * Returns the request body.
412 	 *
413 	 * @return The request body.
414 	 */
415 	public RequestBodyInfo getRequestBody() { return requestBody; }
416 
417 	/**
418 	 * Returns the response with the given status code.
419 	 *
420 	 * @param status The HTTP status code.
421 	 * @return The response, or <jk>null</jk> if not found.
422 	 */
423 	public Response getResponse(int status) {
424 		return getResponse(String.valueOf(status));
425 	}
426 
427 	/**
428 	 * Returns the response with the given status code.
429 	 *
430 	 * @param status The HTTP status code.  Must not be <jk>null</jk>.
431 	 * @return The response, or <jk>null</jk> if not found.
432 	 */
433 	public Response getResponse(String status) {
434 		assertArgNotNull("status", status);
435 		return responses.get(status);
436 	}
437 
438 	/**
439 	 * Returns the responses map.
440 	 *
441 	 * @return The responses map.
442 	 */
443 	public Map<String,Response> getResponses() { return nullIfEmpty(responses); }
444 
445 	/**
446 	 * Returns the security requirements list.
447 	 *
448 	 * @return The security requirements list.
449 	 */
450 	public List<SecurityRequirement> getSecurity() { return nullIfEmpty(security); }
451 
452 	/**
453 	 * Returns the servers list.
454 	 *
455 	 * @return The servers list.
456 	 */
457 	public List<Server> getServers() { return nullIfEmpty(servers); }
458 
459 	/**
460 	 * Returns the summary.
461 	 *
462 	 * @return The summary.
463 	 */
464 	public String getSummary() { return summary; }
465 
466 	/**
467 	 * Returns the tags list.
468 	 *
469 	 * @return The tags list.
470 	 */
471 	public List<String> getTags() { return nullIfEmpty(tags); }
472 
473 	@Override /* Overridden from OpenApiElement */
474 	public Set<String> keySet() {
475 		// @formatter:off
476 		var s = setb(String.class)
477 			.addIf(ne(callbacks), "callbacks")
478 			.addIf(nn(deprecated), "deprecated")
479 			.addIf(nn(description), "description")
480 			.addIf(nn(externalDocs), "externalDocs")
481 			.addIf(nn(operationId), "operationId")
482 			.addIf(ne(parameters), "parameters")
483 			.addIf(nn(requestBody), "requestBody")
484 			.addIf(ne(responses), "responses")
485 			.addIf(ne(security), "security")
486 			.addIf(ne(servers), "servers")
487 			.addIf(nn(summary), "summary")
488 			.addIf(ne(tags), "tags")
489 			.build();
490 		// @formatter:on
491 		return new MultiSet<>(s, super.keySet());
492 	}
493 
494 	@Override /* Overridden from OpenApiElement */
495 	public Operation set(String property, Object value) {
496 		assertArgNotNull("property", property);
497 		return switch (property) {
498 			case "callbacks" -> setCallbacks(toMapBuilder(value, String.class, Callback.class).sparse().build());
499 			case "deprecated" -> setDeprecated(toType(value, Boolean.class));
500 			case "description" -> setDescription(s(value));
501 			case "externalDocs" -> setExternalDocs(toType(value, ExternalDocumentation.class));
502 			case "operationId" -> setOperationId(s(value));
503 			case "parameters" -> setParameters(toListBuilder(value, Parameter.class).sparse().build());
504 			case "requestBody" -> setRequestBody(toType(value, RequestBodyInfo.class));
505 			case "responses" -> setResponses(toMapBuilder(value, String.class, Response.class).sparse().build());
506 			case "security" -> setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build());
507 			case "servers" -> setServers(toListBuilder(value, Server.class).sparse().build());
508 			case "summary" -> setSummary(s(value));
509 			case "tags" -> setTags(toListBuilder(value, String.class).sparse().build());
510 			default -> {
511 				super.set(property, value);
512 				yield this;
513 			}
514 		};
515 	}
516 
517 	/**
518 	 * Sets the callbacks map.
519 	 *
520 	 * @param value The new value for this property.
521 	 * @return This object.
522 	 */
523 	public Operation setCallbacks(Map<String,Callback> value) {
524 		callbacks.clear();
525 		if (nn(value))
526 			callbacks.putAll(value);
527 		return this;
528 	}
529 
530 	/**
531 	 * Sets the deprecated flag.
532 	 *
533 	 * @param value The new value for this property.
534 	 * @return This object.
535 	 */
536 	public Operation setDeprecated(Boolean value) {
537 		deprecated = value;
538 		return this;
539 	}
540 
541 	/**
542 	 * Sets the description.
543 	 *
544 	 * @param value The new value for this property.
545 	 * @return This object.
546 	 */
547 	public Operation setDescription(String value) {
548 		description = value;
549 		return this;
550 	}
551 
552 	/**
553 	 * Sets the external documentation.
554 	 *
555 	 * @param value The new value for this property.
556 	 * @return This object.
557 	 */
558 	public Operation setExternalDocs(ExternalDocumentation value) {
559 		externalDocs = value;
560 		return this;
561 	}
562 
563 	/**
564 	 * Sets the operation ID.
565 	 *
566 	 * @param value The new value for this property.
567 	 * @return This object.
568 	 */
569 	public Operation setOperationId(String value) {
570 		operationId = value;
571 		return this;
572 	}
573 
574 	/**
575 	 * Sets the parameters list.
576 	 *
577 	 * @param value The new value for this property.
578 	 * @return This object.
579 	 */
580 	public Operation setParameters(List<Parameter> value) {
581 		parameters.clear();
582 		if (nn(value))
583 			parameters.addAll(value);
584 		return this;
585 	}
586 
587 	/**
588 	 * Sets the parameters list.
589 	 *
590 	 * @param value The new value for this property.
591 	 * @return This object.
592 	 */
593 	public Operation setParameters(Parameter...value) {
594 		setParameters(toListBuilder(value, Parameter.class).sparse().build());
595 		return this;
596 	}
597 
598 	/**
599 	 * Sets the request body.
600 	 *
601 	 * @param value The new value for this property.
602 	 * @return This object.
603 	 */
604 	public Operation setRequestBody(RequestBodyInfo value) {
605 		requestBody = value;
606 		return this;
607 	}
608 
609 	/**
610 	 * Sets the responses map.
611 	 *
612 	 * @param value The new value for this property.
613 	 * @return This object.
614 	 */
615 	public Operation setResponses(Map<String,Response> value) {
616 		responses.clear();
617 		if (nn(value))
618 			responses.putAll(value);
619 		return this;
620 	}
621 
622 	/**
623 	 * Sets the security requirements list.
624 	 *
625 	 * @param value The new value for this property.
626 	 * @return This object.
627 	 */
628 	public Operation setSecurity(List<SecurityRequirement> value) {
629 		security.clear();
630 		if (nn(value))
631 			security.addAll(value);
632 		return this;
633 	}
634 
635 	/**
636 	 * Sets the security requirements list.
637 	 *
638 	 * @param value The new value for this property.
639 	 * @return This object.
640 	 */
641 	public Operation setSecurity(SecurityRequirement...value) {
642 		setSecurity(toListBuilder(value, SecurityRequirement.class).sparse().build());
643 		return this;
644 	}
645 
646 	/**
647 	 * Sets the servers list.
648 	 *
649 	 * @param value The new value for this property.
650 	 * @return This object.
651 	 */
652 	public Operation setServers(List<Server> value) {
653 		servers.clear();
654 		if (nn(value))
655 			servers.addAll(value);
656 		return this;
657 	}
658 
659 	/**
660 	 * Sets the servers list.
661 	 *
662 	 * @param value The new value for this property.
663 	 * @return This object.
664 	 */
665 	public Operation setServers(Server...value) {
666 		setServers(toListBuilder(value, Server.class).sparse().build());
667 		return this;
668 	}
669 
670 	/**
671 	 * Sets the summary.
672 	 *
673 	 * @param value The new value for this property.
674 	 * @return This object.
675 	 */
676 	public Operation setSummary(String value) {
677 		summary = value;
678 		return this;
679 	}
680 
681 	/**
682 	 * Sets the tags list.
683 	 *
684 	 * @param value The new value for this property.
685 	 * @return This object.
686 	 */
687 	public Operation setTags(List<String> value) {
688 		tags.clear();
689 		if (nn(value))
690 			tags.addAll(value);
691 		return this;
692 	}
693 
694 	/**
695 	 * Sets the tags list.
696 	 *
697 	 * @param value The new value for this property.
698 	 * @return This object.
699 	 */
700 	public Operation setTags(String...value) {
701 		setTags(toListBuilder(value, String.class).sparse().build());
702 		return this;
703 	}
704 
705 	@Override /* Overridden from OpenApiElement */
706 	public Operation strict() {
707 		super.strict();
708 		return this;
709 	}
710 
711 	@Override /* Overridden from OpenApiElement */
712 	public Operation strict(Object value) {
713 		super.strict(value);
714 		return this;
715 	}
716 }