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