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