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.oapi; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016import static org.apache.juneau.common.internal.StringUtils.*; 017import static org.apache.juneau.httppart.HttpPartCollectionFormat.*; 018import static org.apache.juneau.httppart.HttpPartDataType.*; 019import static org.apache.juneau.httppart.HttpPartFormat.*; 020 021import java.io.*; 022import java.lang.reflect.*; 023import java.nio.charset.*; 024import java.util.*; 025import java.util.function.*; 026 027import org.apache.juneau.*; 028import org.apache.juneau.collections.*; 029import org.apache.juneau.httppart.*; 030import org.apache.juneau.internal.*; 031import org.apache.juneau.parser.*; 032import org.apache.juneau.swap.*; 033import org.apache.juneau.swaps.*; 034import org.apache.juneau.uon.*; 035 036/** 037 * Session object that lives for the duration of a single use of {@link OpenApiParser}. 038 * 039 * <h5 class='section'>Notes:</h5><ul> 040 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 041 * </ul> 042 * 043 * <h5 class='section'>See Also:</h5><ul> 044 * <li class='link'><a class="doclink" href="../../../../index.html#jm.OpenApiDetails">OpenAPI Details</a> 045 046 * </ul> 047 */ 048public class OpenApiParserSession extends UonParserSession { 049 050 //------------------------------------------------------------------------------------------------------------------- 051 // Static 052 //------------------------------------------------------------------------------------------------------------------- 053 054 // Cache these for faster lookup 055 private static final BeanContext BC = BeanContext.DEFAULT; 056 private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class); 057 private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class); 058 private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class); 059 private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class); 060 private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class); 061 private static final ClassMeta<JsonList> CM_JsonList = BC.getClassMeta(JsonList.class); 062 private static final ClassMeta<JsonMap> CM_JsonMap = BC.getClassMeta(JsonMap.class); 063 064 private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT; 065 066 /** 067 * Creates a new builder for this object. 068 * 069 * @param ctx The context creating this session. 070 * @return A new builder. 071 */ 072 public static Builder create(OpenApiParser ctx) { 073 return new Builder(ctx); 074 } 075 076 //------------------------------------------------------------------------------------------------------------------- 077 // Builder 078 //------------------------------------------------------------------------------------------------------------------- 079 080 /** 081 * Builder class. 082 */ 083 @FluentSetters 084 public static class Builder extends UonParserSession.Builder { 085 086 OpenApiParser ctx; 087 088 /** 089 * Constructor 090 * 091 * @param ctx The context creating this session. 092 */ 093 protected Builder(OpenApiParser ctx) { 094 super(ctx); 095 this.ctx = ctx; 096 } 097 098 @Override 099 public OpenApiParserSession build() { 100 return new OpenApiParserSession(this); 101 } 102 103 // <FluentSetters> 104 105 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 106 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 107 super.apply(type, apply); 108 return this; 109 } 110 111 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 112 public Builder debug(Boolean value) { 113 super.debug(value); 114 return this; 115 } 116 117 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 118 public Builder properties(Map<String,Object> value) { 119 super.properties(value); 120 return this; 121 } 122 123 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 124 public Builder property(String key, Object value) { 125 super.property(key, value); 126 return this; 127 } 128 129 @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */ 130 public Builder unmodifiable() { 131 super.unmodifiable(); 132 return this; 133 } 134 135 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 136 public Builder locale(Locale value) { 137 super.locale(value); 138 return this; 139 } 140 141 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 142 public Builder localeDefault(Locale value) { 143 super.localeDefault(value); 144 return this; 145 } 146 147 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 148 public Builder mediaType(MediaType value) { 149 super.mediaType(value); 150 return this; 151 } 152 153 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 154 public Builder mediaTypeDefault(MediaType value) { 155 super.mediaTypeDefault(value); 156 return this; 157 } 158 159 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 160 public Builder timeZone(TimeZone value) { 161 super.timeZone(value); 162 return this; 163 } 164 165 @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */ 166 public Builder timeZoneDefault(TimeZone value) { 167 super.timeZoneDefault(value); 168 return this; 169 } 170 171 @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */ 172 public Builder javaMethod(Method value) { 173 super.javaMethod(value); 174 return this; 175 } 176 177 @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */ 178 public Builder outer(Object value) { 179 super.outer(value); 180 return this; 181 } 182 183 @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */ 184 public Builder schema(HttpPartSchema value) { 185 super.schema(value); 186 return this; 187 } 188 189 @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */ 190 public Builder schemaDefault(HttpPartSchema value) { 191 super.schemaDefault(value); 192 return this; 193 } 194 195 @Override /* GENERATED - org.apache.juneau.parser.ReaderParserSession.Builder */ 196 public Builder fileCharset(Charset value) { 197 super.fileCharset(value); 198 return this; 199 } 200 201 @Override /* GENERATED - org.apache.juneau.parser.ReaderParserSession.Builder */ 202 public Builder streamCharset(Charset value) { 203 super.streamCharset(value); 204 return this; 205 } 206 207 @Override /* GENERATED - org.apache.juneau.uon.UonParserSession.Builder */ 208 public Builder decoding(boolean value) { 209 super.decoding(value); 210 return this; 211 } 212 213 // </FluentSetters> 214 } 215 216 //------------------------------------------------------------------------------------------------------------------- 217 // Instance 218 //------------------------------------------------------------------------------------------------------------------- 219 220 private final OpenApiParser ctx; 221 222 /** 223 * Constructor. 224 * 225 * @param builder The builder for this object. 226 */ 227 protected OpenApiParserSession(Builder builder) { 228 super(builder); 229 ctx = builder.ctx; 230 } 231 232 233 @SuppressWarnings("unchecked") 234 @Override /* HttpPartParser */ 235 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException { 236 if (partType == null) 237 partType = HttpPartType.OTHER; 238 239 boolean isOptional = type.isOptional(); 240 241 while (type != null && type.isOptional()) 242 type = (ClassMeta<T>)type.getElementType(); 243 244 if (type == null) 245 type = (ClassMeta<T>)object(); 246 247 schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA); 248 249 T t = parseInner(partType, schema, in, type); 250 if (t == null && type.isPrimitive()) 251 t = type.getPrimitiveDefault(); 252 schema.validateOutput(t, ctx.getBeanContext()); 253 254 if (isOptional) 255 t = (T)optional(t); 256 257 return t; 258 } 259 260 @Override /* ParserSession */ 261 protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 262 return parseInner(null, HttpPartSchema.DEFAULT, pipe.asString(), type); 263 } 264 265 @SuppressWarnings({ "unchecked" }) 266 private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException { 267 schema.validateInput(in); 268 if (in == null || "null".equals(in)) { 269 if (schema.getDefault() == null) 270 return null; 271 in = schema.getDefault(); 272 } else { 273 274 ObjectSwap<T,Object> swap = (ObjectSwap<T,Object>)type.getSwap(this); 275 BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)type.getBuilderSwap(this); 276 ClassMeta<?> sType = null; 277 if (builder != null) 278 sType = builder.getBuilderClassMeta(this); 279 else if (swap != null) 280 sType = swap.getSwapClassMeta(this); 281 else 282 sType = type; 283 284 if (sType.isOptional()) 285 return (T)optional(parseInner(partType, schema, in, sType.getElementType())); 286 287 HttpPartDataType t = schema.getType(sType); 288 if (partType == null) 289 partType = HttpPartType.OTHER; 290 291 HttpPartFormat f = schema.getFormat(sType); 292 if (f == HttpPartFormat.NO_FORMAT) 293 f = ctx.getFormat(); 294 295 if (t == STRING) { 296 if (sType.isObject()) { 297 if (f == BYTE) 298 return toType(base64Decode(in), type); 299 if (f == DATE || f == DATE_TIME) 300 return toType(parseIsoCalendar(in), type); 301 if (f == BINARY) 302 return toType(fromHex(in), type); 303 if (f == BINARY_SPACED) 304 return toType(fromSpacedHex(in), type); 305 if (f == HttpPartFormat.UON) 306 return super.parse(partType, schema, in, type); 307 return toType(in, type); 308 } 309 if (f == BYTE) 310 return toType(base64Decode(in), type); 311 if (f == DATE) { 312 try { 313 if (type.isCalendar()) 314 return toType(TemporalCalendarSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 315 if (type.isDate()) 316 return toType(TemporalDateSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 317 if (type.isTemporal()) 318 return toType(TemporalSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 319 return toType(in, type); 320 } catch (Exception e) { 321 throw new ParseException(e); 322 } 323 } 324 if (f == DATE_TIME) { 325 try { 326 if (type.isCalendar()) 327 return toType(TemporalCalendarSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 328 if (type.isDate()) 329 return toType(TemporalDateSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 330 if (type.isTemporal()) 331 return toType(TemporalSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 332 return toType(in, type); 333 } catch (Exception e) { 334 throw new ParseException(e); 335 } 336 } 337 if (f == BINARY) 338 return toType(fromHex(in), type); 339 if (f == BINARY_SPACED) 340 return toType(fromSpacedHex(in), type); 341 if (f == HttpPartFormat.UON) 342 return super.parse(partType, schema, in, type); 343 return toType(in, type); 344 345 } else if (t == BOOLEAN) { 346 if (type.isObject()) 347 type = (ClassMeta<T>)CM_Boolean; 348 if (type.isBoolean()) 349 return super.parse(partType, schema, in, type); 350 return toType(super.parse(partType, schema, in, CM_Boolean), type); 351 352 } else if (t == INTEGER) { 353 if (type.isObject()) { 354 if (f == INT64) 355 type = (ClassMeta<T>)CM_Long; 356 else 357 type = (ClassMeta<T>)CM_Integer; 358 } 359 if (type.isNumber()) 360 return super.parse(partType, schema, in, type); 361 return toType(super.parse(partType, schema, in, CM_Integer), type); 362 363 } else if (t == NUMBER) { 364 if (type.isObject()) { 365 if (f == DOUBLE) 366 type = (ClassMeta<T>)CM_Double; 367 else 368 type = (ClassMeta<T>)CM_Float; 369 } 370 if (type.isNumber()) 371 return super.parse(partType, schema, in, type); 372 return toType(super.parse(partType, schema, in, CM_Double), type); 373 374 } else if (t == ARRAY) { 375 376 HttpPartCollectionFormat cf = schema.getCollectionFormat(); 377 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 378 cf = ctx.getCollectionFormat(); 379 380 if (cf == HttpPartCollectionFormat.UONC) 381 return super.parse(partType, schema, in, type); 382 383 if (type.isObject()) 384 type = (ClassMeta<T>)CM_JsonList; 385 386 ClassMeta<?> eType = type.isObject() ? string() : type.getElementType(); 387 if (eType == null) 388 eType = schema.getParsedType().getElementType(); 389 if (eType == null) 390 eType = string(); 391 392 String[] ss = {}; 393 394 if (cf == MULTI) 395 ss = new String[]{in}; 396 else if (cf == CSV) 397 ss = split(in, ','); 398 else if (cf == PIPES) 399 ss = split(in, '|'); 400 else if (cf == SSV) 401 ss = splitQuoted(in); 402 else if (cf == TSV) 403 ss = split(in, '\t'); 404 else if (cf == HttpPartCollectionFormat.UONC) 405 return super.parse(partType, null, in, type); 406 else if (cf == NO_COLLECTION_FORMAT) { 407 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 408 return super.parse(partType, null, in, type); 409 ss = split(in, ','); 410 } 411 412 HttpPartSchema items = schema.getItems(); 413 if (items == null) 414 items = HttpPartSchema.DEFAULT; 415 Object o = Array.newInstance(eType.getInnerClass(), ss.length); 416 for (int i = 0; i < ss.length; i++) 417 Array.set(o, i, parse(partType, items, ss[i], eType)); 418 if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type)) 419 return toType(toType(o, schema.getParsedType()), type); 420 return toType(o, type); 421 422 } else if (t == OBJECT) { 423 424 HttpPartCollectionFormat cf = schema.getCollectionFormat(); 425 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 426 cf = ctx.getCollectionFormat(); 427 428 if (cf == HttpPartCollectionFormat.UONC) 429 return super.parse(partType, schema, in, type); 430 431 if (type.isObject()) 432 type = (ClassMeta<T>)CM_JsonMap; 433 434 if (! type.isMapOrBean()) 435 throw new ParseException("Invalid type {0} for part type OBJECT.", type); 436 437 String[] ss = {}; 438 439 if (cf == MULTI) 440 ss = new String[]{in}; 441 else if (cf == CSV) 442 ss = split(in, ','); 443 else if (cf == PIPES) 444 ss = split(in, '|'); 445 else if (cf == SSV) 446 ss = splitQuoted(in); 447 else if (cf == TSV) 448 ss = split(in, '\t'); 449 else if (cf == HttpPartCollectionFormat.UONC) 450 return super.parse(partType, null, in, type); 451 else if (cf == NO_COLLECTION_FORMAT) { 452 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 453 return super.parse(partType, null, in, type); 454 ss = split(in, ','); 455 } 456 457 if (type.isBean()) { 458 BeanMap<T> m = ctx.getBeanContext().newBeanMap(type.getInnerClass()); 459 for (String s : ss) { 460 String[] kv = split(s, '=', 2); 461 if (kv.length != 2) 462 throw new ParseException("Invalid input {0} for part type OBJECT.", in); 463 String key = kv[0], value = kv[1]; 464 BeanPropertyMeta bpm = m.getPropertyMeta(key); 465 if (bpm == null && ! isIgnoreUnknownBeanProperties()) 466 throw new ParseException("Invalid input {0} for part type OBJECT. Cannot find property {1}", in, key); 467 m.put(key, parse(partType, schema.getProperty(key), value, ((ClassMeta<T>)(bpm == null ? object() : bpm.getClassMeta())))); 468 } 469 return m.getBean(); 470 } 471 472 ClassMeta<?> eType = type.isObject() ? string() : type.getValueType(); 473 if (eType == null) 474 eType = schema.getParsedType().getValueType(); 475 if (eType == null) 476 eType = string(); 477 478 try { 479 Map<String,Object> m = (Map<String,Object>)type.newInstance(); 480 if (m == null) 481 m = JsonMap.create(); 482 483 for (String s : ss) { 484 String[] kv = split(s, '=', 2); 485 if (kv.length != 2) 486 throw new ParseException("Invalid input {0} for part type OBJECT.", in); 487 String key = kv[0], value = kv[1]; 488 m.put(key, parse(partType, schema.getProperty(key), value, eType)); 489 } 490 return (T)m; 491 } catch (ExecutableException e) { 492 throw new ParseException(e); 493 } 494 495 } else if (t == FILE) { 496 throw new ParseException("File part not supported."); 497 498 } else if (t == NO_TYPE) { 499 // This should never be returned by HttpPartSchema.getType(ClassMeta). 500 throw new ParseException("Invalid type."); 501 } 502 } 503 504 return super.parse(partType, schema, in, type); 505 } 506 507 private <T> T toType(Object in, ClassMeta<T> type) throws ParseException { 508 try { 509 return convertToType(in, type); 510 } catch (InvalidDataConversionException e) { 511 throw new ParseException(e.getMessage()); 512 } 513 } 514}