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<String,{@link HeaderInfo}|String></code> 307 * <li><code>String</code> - JSON object representation of <code>Map<String,{@link HeaderInfo}></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<String,Object></code> 428 * <li><code>String</code> - JSON object representation of <code>Map<String,Object></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}