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.dto.swagger;
014
015import static org.apache.juneau.internal.BeanPropertyUtils.*;
016import java.util.*;
017
018import org.apache.juneau.annotation.*;
019import org.apache.juneau.internal.*;
020import org.apache.juneau.utils.*;
021
022/**
023 * Describes a single response from an API Operation.
024 *
025 * <h5 class='section'>Example:</h5>
026 * <p class='bcode w800'>
027 *    <jc>// Construct using SwaggerBuilder.</jc>
028 *    ResponseInfo x = <jsm>responseInfo</jsm>(<js>"A complex object array response"</js>)
029 *       .schema(
030 *          <jsm>schemaInfo</jsm>
031 *             .type(<js>"array"</js>)
032 *             .items(
033 *                <jsm>items<jsm>()
034 *                   .set(<js>"$ref"</js>, <js>"#/definitions/VeryComplexType"</js>)
035 *             )
036 *       );
037 *
038 *    <jc>// Serialize using JsonSerializer.</jc>
039 *    String json = JsonSerializer.<jsf>DEFAULT</jsf>.toString(x);
040 *
041 *    <jc>// Or just use toString() which does the same as above.</jc>
042 *    String json = x.toString();
043 * </p>
044 * <p class='bcode w800'>
045 *    <jc>// Output</jc>
046 *    {
047 *       <js>"description"</js>: <js>"A complex object array response"</js>,
048 *       <js>"schema"</js>: {
049 *          <js>"type"</js>: <js>"array"</js>,
050 *          <js>"items"</js>: {
051 *             <js>"$ref"</js>: <js>"#/definitions/VeryComplexType"</js>
052 *          }
053 *       }
054 *    }
055 * </p>
056 *
057 * <h5 class='section'>See Also:</h5>
058 * <ul class='doctree'>
059 *    <li class='link'>{@doc juneau-dto.Swagger}
060 * </ul>
061 */
062@Bean(properties="description,schema,headers,x-example,examples,*")
063public class ResponseInfo extends SwaggerElement {
064
065   private String description;
066   private SchemaInfo schema;
067   private Map<String,HeaderInfo> headers;
068   private Object example;
069   private Map<String,Object> examples;
070
071   /**
072    * Default constructor.
073    */
074   public ResponseInfo() {}
075
076   /**
077    * Copy constructor.
078    *
079    * @param copyFrom The object to copy.
080    */
081   public ResponseInfo(ResponseInfo copyFrom) {
082      super(copyFrom);
083
084      this.description = copyFrom.description;
085      this.schema = copyFrom.schema == null ? null : copyFrom.schema.copy();
086
087      this.headers = copyFrom.headers == null ? null : new LinkedHashMap<String,HeaderInfo>();
088      if (copyFrom.headers != null)
089         for (Map.Entry<String,HeaderInfo> e : copyFrom.headers.entrySet())
090            this.headers.put(e.getKey(),  e.getValue().copy());
091
092      this.example = copyFrom.example;
093
094      this.examples = copyFrom.examples == null ? null : new LinkedHashMap<String,Object>();
095      if (copyFrom.examples != null)
096         this.examples.putAll(copyFrom.examples);
097   }
098
099   /**
100    * Make a deep copy of this object.
101    *
102    * @return A deep copy of this object.
103    */
104   public ResponseInfo copy() {
105      return new ResponseInfo(this);
106   }
107
108   /**
109    * Copies any non-null fields from the specified object to this object.
110    *
111    * @param r
112    *    The object to copy fields from.
113    *    <br>Can be <jk>null</jk>.
114    * @return This object (for method chaining).
115    */
116   public ResponseInfo copyFrom(ResponseInfo r) {
117      if (r != null) {
118         if (r.description != null)
119            description = r.description;
120         if (r.schema != null)
121            schema = r.schema;
122         if (r.headers != null)
123            headers = r.headers;
124         if (r.example != null)
125            example = r.example;
126         if (r.examples != null)
127            examples = r.examples;
128      }
129      return this;
130   }
131
132   /**
133    * Bean property getter:  <property>description</property>.
134    *
135    * <p>
136    * A short description of the response.
137    *
138    * @return The property value, or <jk>null</jk> if it is not set.
139    */
140   public String getDescription() {
141      return description;
142   }
143
144   /**
145    * Bean property setter:  <property>description</property>.
146    *
147    * <p>
148    * A short description of the response.
149    *
150    * @param value
151    *    The new value for this property.
152    *    <br>{@doc GFM} can be used for rich text representation.
153    *    <br>Property value is required.
154    * @return This object (for method chaining).
155    */
156   public ResponseInfo setDescription(String value) {
157      description = value;
158      return this;
159   }
160
161   /**
162    * Same as {@link #setDescription(String)}.
163    *
164    * @param value
165    *    The new value for this property.
166    *    <br>Non-String values will be converted to String using <code>toString()</code>.
167    *    <br>Can be <jk>null</jk> to unset the property.
168    * @return This object (for method chaining).
169    */
170   public ResponseInfo description(Object value) {
171      return setDescription(toStringVal(value));
172   }
173
174   /**
175    * Bean property getter:  <property>schema</property>.
176    *
177    * <p>
178    * A definition of the response structure.
179    *
180    * <h5 class='section'>Notes:</h5>
181    * <ul class='spaced-list'>
182    *    <li>
183    *       If this field does not exist, it means no content is returned as part of the response.
184    *    <li>
185    *       As an extension to the {@doc SwaggerSchemaObject Schema Object},
186    *       its root type value may also be <js>"file"</js>.
187    *    <li>
188    *       This SHOULD be accompanied by a relevant produces mime-type.
189    * </ul>
190    *
191    * @return The property value, or <jk>null</jk> if it is not set.
192    */
193   public SchemaInfo getSchema() {
194      return schema;
195   }
196
197   /**
198    * Bean property setter:  <property>schema</property>.
199    *
200    * <p>
201    * A definition of the response structure.
202    *
203    * <h5 class='section'>Notes:</h5>
204    * <ul class='spaced-list'>
205    *    <li>
206    *       If this field does not exist, it means no content is returned as part of the response.
207    *    <li>
208    *       As an extension to the {@doc SwaggerSchemaObject Schema Object},
209    *       its root type value may also be <js>"file"</js>.
210    *    <li>
211    *       This SHOULD be accompanied by a relevant produces mime-type.
212    * </ul>
213    *
214    * @param value
215    *    The new value for this property.
216    *    <br>It can be a primitive, an array or an object.
217    *    <br>Can be <jk>null</jk> to unset the property.
218    * @return This object (for method chaining).
219    */
220   public ResponseInfo setSchema(SchemaInfo value) {
221      schema = value;
222      return this;
223   }
224
225   /**
226    * Same as {@link #setSchema(SchemaInfo)}.
227    *
228    * @param value
229    *    The new value for this property.
230    *    <br>Valid types:
231    *    <ul>
232    *       <li>{@link SchemaInfo}
233    *       <li><code>String</code> - JSON object representation of {@link SchemaInfo}
234    *          <h5 class='figure'>Example:</h5>
235    *          <p class='bcode w800'>
236    *    schema(<js>"{type:'type',description:'description',...}"</js>);
237    *          </p>
238    *    </ul>
239    *    <br>Can be <jk>null</jk> to unset the property.
240    * @return This object (for method chaining).
241    */
242   public ResponseInfo schema(Object value) {
243      return setSchema(toType(value, SchemaInfo.class));
244   }
245
246   /**
247    * Bean property getter:  <property>headers</property>.
248    *
249    * <p>
250    * A list of headers that are sent with the response.
251    *
252    * @return The property value, or <jk>null</jk> if it is not set.
253    */
254   public Map<String,HeaderInfo> getHeaders() {
255      return headers;
256   }
257
258   /**
259    * Bean property setter:  <property>headers</property>.
260    *
261    * <p>
262    * A list of headers that are sent with the response.
263    *
264    * @param value
265    *    The new value for this property.
266    *    <br>Can be <jk>null</jk> to unset the property.
267    * @return This object (for method chaining).
268    */
269   public ResponseInfo setHeaders(Map<String,HeaderInfo> value) {
270      headers = newMap(value);
271      return this;
272   }
273
274   /**
275    * Adds one or more values to the <property>headers</property> property.
276    *
277    * @param values
278    *    The values to add to this property.
279    *    <br>Ignored if <jk>null</jk>.
280    * @return This object (for method chaining).
281    */
282   public ResponseInfo addHeaders(Map<String,HeaderInfo> values) {
283      headers = addToMap(headers, values);
284      return this;
285   }
286
287   /**
288    * Adds a single value to the <property>headers</property> property.
289    *
290    * @param name The header name.
291    * @param header The header descriptions
292    * @return This object (for method chaining).
293    */
294   public ResponseInfo header(String name, HeaderInfo header) {
295      addHeaders(Collections.singletonMap(name, header));
296      return this;
297   }
298
299   /**
300    * Adds one or more values to the <property>headers</property> property.
301    *
302    * @param values
303    *    The values to add to this property.
304    *    <br>Valid types:
305    *    <ul>
306    *       <li><code>Map&lt;String,{@link HeaderInfo}|String&gt;</code>
307    *       <li><code>String</code> - JSON object representation of <code>Map&lt;String,{@link HeaderInfo}&gt;</code>
308    *          <h5 class='figure'>Example:</h5>
309    *          <p class='bcode w800'>
310    *    headers(<js>"{headerName:{description:'description',...}}"</js>);
311    *          </p>
312    *    </ul>
313    *    <br>Ignored if <jk>null</jk>.
314    * @return This object (for method chaining).
315    */
316   public ResponseInfo headers(Object...values) {
317      headers = addToMap(headers, values, String.class, HeaderInfo.class);
318      return this;
319   }
320
321   /**
322    * Returns the header information with the specified name.
323    *
324    * @param name The header name.
325    * @return The header info, or <jk>null</jk> if not found.
326    */
327   public HeaderInfo getHeader(String name) {
328      return getHeaders().get(name);
329   }
330
331   /**
332    * Bean property getter:  <property>x-example</property>.
333    *
334    * @return The property value, or <jk>null</jk> if it is not set.
335    */
336   @BeanProperty("x-example")
337   public Object getExample() {
338      return example;
339   }
340
341   /**
342    * Bean property setter:  <property>x-example</property>.
343    *
344    * @param value
345    *    The new value for this property.
346    *    <br>Can be <jk>null</jk> to unset the property.
347    * @return This object (for method chaining).
348    */
349   @BeanProperty("x-example")
350   public ResponseInfo setExample(Object value) {
351      example = value;
352      return this;
353   }
354
355   /**
356    * Bean property setter:  <property>x-example</property>.
357    *
358    * @param value The property value.
359    * @return This object (for method chaining).
360    */
361   public ResponseInfo example(Object value) {
362      example = value;
363      return this;
364   }
365
366   /**
367    * Bean property getter:  <property>examples</property>.
368    *
369    * <p>
370    * An example of the response message.
371    *
372    * @return The property value, or <jk>null</jk> if it is not set.
373    */
374   public Map<String,Object> getExamples() {
375      return examples;
376   }
377
378   /**
379    * Bean property setter:  <property>examples</property>.
380    *
381    * <p>
382    * An example of the response message.
383    *
384    * @param value
385    *    The new value for this property.
386    *    <br>Keys must be MIME-type strings.
387    *    <br>Can be <jk>null</jk> to unset the property.
388    * @return This object (for method chaining).
389    */
390   public ResponseInfo setExamples(Map<String,Object> value) {
391      examples = newMap(value);
392      return this;
393   }
394
395   /**
396    * Adds one or more values to the <property>examples</property> property.
397    *
398    * @param values
399    *    The values to add to this property.
400    *    <br>Ignored if <jk>null</jk>.
401    * @return This object (for method chaining).
402    */
403   public ResponseInfo addExamples(Map<String,Object> values) {
404      examples = addToMap(examples, values);
405      return this;
406   }
407
408   /**
409    * Adds a single value to the <property>examples</property> property.
410    *
411    * @param mimeType The mime-type string.
412    * @param example The example.
413    * @return This object (for method chaining).
414    */
415   public ResponseInfo example(String mimeType, Object example) {
416      examples = addToMap(examples, mimeType, example);
417      return this;
418   }
419
420   /**
421    * Adds one or more values to the <property>examples</property> property.
422    *
423    * @param values
424    *    The values to add to this property.
425    *    <br>Valid types:
426    *    <ul>
427    *       <li><code>Map&lt;String,Object&gt;</code>
428    *       <li><code>String</code> - JSON object representation of <code>Map&lt;String,Object&gt;</code>
429    *          <h5 class='figure'>Example:</h5>
430    *          <p class='bcode w800'>
431    *    examples(<js>"{'text/json':{foo:'bar'}}"</js>);
432    *          </p>
433    *    </ul>
434    *    <br>Ignored if <jk>null</jk>.
435    * @return This object (for method chaining).
436    */
437   public ResponseInfo examples(Object...values) {
438      examples = addToMap(examples, values, String.class, Object.class);
439      return this;
440   }
441
442   @Override /* SwaggerElement */
443   public <T> T get(String property, Class<T> type) {
444      if (property == null)
445         return null;
446      switch (property) {
447         case "description": return toType(getDescription(), type);
448         case "schema": return toType(getSchema(), type);
449         case "headers": return toType(getHeaders(), type);
450         case "example": return toType(getExample(), type);
451         case "examples": return toType(getExamples(), type);
452         default: return super.get(property, type);
453      }
454   }
455
456   @Override /* SwaggerElement */
457   public ResponseInfo set(String property, Object value) {
458      if (property == null)
459         return this;
460      switch (property) {
461         case "description": return description(value);
462         case "schema": return schema(value);
463         case "headers": return setHeaders(null).headers(value);
464         case "example": return setExample(value);
465         case "examples": return setExamples(null).examples(value);
466         default:
467            super.set(property, value);
468            return this;
469      }
470   }
471
472   @Override /* SwaggerElement */
473   public Set<String> keySet() {
474      ASet<String> s = new ASet<String>()
475         .appendIf(description != null, "description")
476         .appendIf(schema != null, "schema")
477         .appendIf(headers != null, "headers")
478         .appendIf(example != null, "example")
479         .appendIf(examples != null, "examples");
480      return new MultiSet<>(s, super.keySet());
481   }
482
483   /**
484    * Returns <jk>true</jk> if this response info has headers associated with it.
485    *
486    * @return <jk>true</jk> if this response info has headers associated with it.
487    */
488   public boolean hasHeaders() {
489      return headers != null && ! headers.isEmpty();
490   }
491
492   /**
493    * Resolves any <js>"$ref"</js> attributes in this element.
494    *
495    * @param swagger The swagger document containing the definitions.
496    * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
497    * @param maxDepth
498    *    The maximum depth to resolve references.
499    *    <br>After that level is reached, <code>$ref</code> references will be left alone.
500    *    <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex.
501    * @return
502    *    This object with references resolved.
503    *    <br>May or may not be the same object.
504    */
505   public ResponseInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
506
507      if (schema != null)
508         schema = schema.resolveRefs(swagger, refStack, maxDepth);
509
510      if (headers != null)
511         for (Map.Entry<String,HeaderInfo> e : headers.entrySet())
512            e.setValue(e.getValue().resolveRefs(swagger, refStack, maxDepth));
513
514      return this;
515   }
516}