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