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.StringUtils.*; 016import static org.apache.juneau.httppart.HttpPartCollectionFormat.*; 017import static org.apache.juneau.httppart.HttpPartDataType.*; 018import static org.apache.juneau.httppart.HttpPartFormat.*; 019 020import java.io.*; 021import java.lang.reflect.*; 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.collections.*; 026import org.apache.juneau.httppart.*; 027import org.apache.juneau.internal.*; 028import org.apache.juneau.parser.*; 029import org.apache.juneau.transform.*; 030import org.apache.juneau.transforms.*; 031import org.apache.juneau.uon.*; 032 033/** 034 * Session object that lives for the duration of a single use of {@link OpenApiParser}. 035 * 036 * <p> 037 * This class is NOT thread safe. 038 * It is typically discarded after one-time use although it can be reused within the same thread. 039 */ 040public class OpenApiParserSession extends UonParserSession { 041 042 // Cache these for faster lookup 043 private static final BeanContext BC = BeanContext.DEFAULT; 044 private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class); 045 private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class); 046 private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class); 047 private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class); 048 private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class); 049 private static final ClassMeta<OList> CM_OList = BC.getClassMeta(OList.class); 050 private static final ClassMeta<OMap> CM_OMap = BC.getClassMeta(OMap.class); 051 052 private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT; 053 054 //------------------------------------------------------------------------------------------------------------------- 055 // Instance 056 //------------------------------------------------------------------------------------------------------------------- 057 058 private final OpenApiParser ctx; 059 060 /** 061 * Create a new session using properties specified in the context. 062 * 063 * @param ctx 064 * The context creating this session object. 065 * The context contains all the configuration settings for this object. 066 * @param args 067 * Runtime session arguments. 068 */ 069 protected OpenApiParserSession(OpenApiParser ctx, ParserSessionArgs args) { 070 super(ctx, args); 071 this.ctx = ctx; 072 } 073 074 075 @SuppressWarnings("unchecked") 076 @Override /* HttpPartParser */ 077 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException { 078 if (partType == null) 079 partType = HttpPartType.OTHER; 080 081 boolean isOptional = type.isOptional(); 082 083 while (type != null && type.isOptional()) 084 type = (ClassMeta<T>)type.getElementType(); 085 086 if (type == null) 087 type = (ClassMeta<T>)object(); 088 089 schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA); 090 091 T t = parseInner(partType, schema, in, type); 092 if (t == null && type.isPrimitive()) 093 t = type.getPrimitiveDefault(); 094 schema.validateOutput(t, ctx); 095 096 if (isOptional) 097 t = (T)Optional.ofNullable(t); 098 099 return t; 100 } 101 102 @Override /* ParserSession */ 103 protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 104 return parseInner(null, HttpPartSchema.DEFAULT, pipe.asString(), type); 105 } 106 107 @SuppressWarnings({ "unchecked" }) 108 private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException { 109 schema.validateInput(in); 110 if (in == null || "null".equals(in)) { 111 if (schema.getDefault() == null) 112 return null; 113 in = schema.getDefault(); 114 } else { 115 116 PojoSwap<T,Object> swap = (PojoSwap<T,Object>)type.getSwap(this); 117 BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)type.getBuilderSwap(this); 118 ClassMeta<?> sType = null; 119 if (builder != null) 120 sType = builder.getBuilderClassMeta(this); 121 else if (swap != null) 122 sType = swap.getSwapClassMeta(this); 123 else 124 sType = type; 125 126 if (sType.isOptional()) 127 return (T)Optional.ofNullable(parseInner(partType, schema, in, sType.getElementType())); 128 129 HttpPartDataType t = schema.getType(sType); 130 if (partType == null) 131 partType = HttpPartType.OTHER; 132 133 HttpPartFormat f = schema.getFormat(sType); 134 if (f == HttpPartFormat.NO_FORMAT) 135 f = ctx.getFormat(); 136 137 if (t == STRING) { 138 if (sType.isObject()) { 139 if (f == BYTE) 140 return toType(base64Decode(in), type); 141 if (f == DATE || f == DATE_TIME) 142 return toType(parseIsoCalendar(in), type); 143 if (f == BINARY) 144 return toType(fromHex(in), type); 145 if (f == BINARY_SPACED) 146 return toType(fromSpacedHex(in), type); 147 if (f == HttpPartFormat.UON) 148 return super.parse(partType, schema, in, type); 149 return toType(in, type); 150 } 151 if (f == BYTE) 152 return toType(base64Decode(in), type); 153 if (f == DATE) { 154 try { 155 if (type.isCalendar()) 156 return toType(TemporalCalendarSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 157 if (type.isDate()) 158 return toType(TemporalDateSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 159 if (type.isTemporal()) 160 return toType(TemporalSwap.IsoDate.DEFAULT.unswap(this, in, type), type); 161 return toType(in, type); 162 } catch (Exception e) { 163 throw new ParseException(e); 164 } 165 } 166 if (f == DATE_TIME) { 167 try { 168 if (type.isCalendar()) 169 return toType(TemporalCalendarSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 170 if (type.isDate()) 171 return toType(TemporalDateSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 172 if (type.isTemporal()) 173 return toType(TemporalSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type); 174 return toType(in, type); 175 } catch (Exception e) { 176 throw new ParseException(e); 177 } 178 } 179 if (f == BINARY) 180 return toType(fromHex(in), type); 181 if (f == BINARY_SPACED) 182 return toType(fromSpacedHex(in), type); 183 if (f == HttpPartFormat.UON) 184 return super.parse(partType, schema, in, type); 185 return toType(in, type); 186 187 } else if (t == BOOLEAN) { 188 if (type.isObject()) 189 type = (ClassMeta<T>)CM_Boolean; 190 if (type.isBoolean()) 191 return super.parse(partType, schema, in, type); 192 return toType(super.parse(partType, schema, in, CM_Boolean), type); 193 194 } else if (t == INTEGER) { 195 if (type.isObject()) { 196 if (f == INT64) 197 type = (ClassMeta<T>)CM_Long; 198 else 199 type = (ClassMeta<T>)CM_Integer; 200 } 201 if (type.isNumber()) 202 return super.parse(partType, schema, in, type); 203 return toType(super.parse(partType, schema, in, CM_Integer), type); 204 205 } else if (t == NUMBER) { 206 if (type.isObject()) { 207 if (f == DOUBLE) 208 type = (ClassMeta<T>)CM_Double; 209 else 210 type = (ClassMeta<T>)CM_Float; 211 } 212 if (type.isNumber()) 213 return super.parse(partType, schema, in, type); 214 return toType(super.parse(partType, schema, in, CM_Double), type); 215 216 } else if (t == ARRAY) { 217 218 HttpPartCollectionFormat cf = schema.getCollectionFormat(); 219 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 220 cf = ctx.getCollectionFormat(); 221 222 if (cf == HttpPartCollectionFormat.UONC) 223 return super.parse(partType, schema, in, type); 224 225 if (type.isObject()) 226 type = (ClassMeta<T>)CM_OList; 227 228 ClassMeta<?> eType = type.isObject() ? string() : type.getElementType(); 229 if (eType == null) 230 eType = schema.getParsedType().getElementType(); 231 if (eType == null) 232 eType = string(); 233 234 String[] ss = new String[0]; 235 236 if (cf == MULTI) 237 ss = new String[]{in}; 238 else if (cf == CSV) 239 ss = split(in, ','); 240 else if (cf == PIPES) 241 ss = split(in, '|'); 242 else if (cf == SSV) 243 ss = splitQuoted(in); 244 else if (cf == TSV) 245 ss = split(in, '\t'); 246 else if (cf == HttpPartCollectionFormat.UONC) 247 return super.parse(partType, null, in, type); 248 else if (cf == NO_COLLECTION_FORMAT) { 249 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 250 return super.parse(partType, null, in, type); 251 ss = split(in, ','); 252 } 253 254 HttpPartSchema items = schema.getItems(); 255 if (items == null) 256 items = HttpPartSchema.DEFAULT; 257 Object o = Array.newInstance(eType.getInnerClass(), ss.length); 258 for (int i = 0; i < ss.length; i++) 259 Array.set(o, i, parse(partType, items, ss[i], eType)); 260 if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type)) 261 return toType(toType(o, schema.getParsedType()), type); 262 return toType(o, type); 263 264 } else if (t == OBJECT) { 265 266 HttpPartCollectionFormat cf = schema.getCollectionFormat(); 267 if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT) 268 cf = ctx.getCollectionFormat(); 269 270 if (cf == HttpPartCollectionFormat.UONC) 271 return super.parse(partType, schema, in, type); 272 273 if (type.isObject()) 274 type = (ClassMeta<T>)CM_OMap; 275 276 if (! type.isMapOrBean()) 277 throw new ParseException("Invalid type {0} for part type OBJECT.", type); 278 279 String[] ss = new String[0]; 280 281 if (cf == MULTI) 282 ss = new String[]{in}; 283 else if (cf == CSV) 284 ss = split(in, ','); 285 else if (cf == PIPES) 286 ss = split(in, '|'); 287 else if (cf == SSV) 288 ss = splitQuoted(in); 289 else if (cf == TSV) 290 ss = split(in, '\t'); 291 else if (cf == HttpPartCollectionFormat.UONC) 292 return super.parse(partType, null, in, type); 293 else if (cf == NO_COLLECTION_FORMAT) { 294 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 295 return super.parse(partType, null, in, type); 296 ss = split(in, ','); 297 } 298 299 if (type.isBean()) { 300 BeanMap<T> m = ctx.createBeanSession().newBeanMap(type.getInnerClass()); 301 for (String s : ss) { 302 String[] kv = split(s, '=', 2); 303 if (kv.length != 2) 304 throw new ParseException("Invalid input {0} for part type OBJECT.", in); 305 String key = kv[0], value = kv[1]; 306 BeanPropertyMeta bpm = m.getPropertyMeta(key); 307 if (bpm == null && ! isIgnoreUnknownBeanProperties()) 308 throw new ParseException("Invalid input {0} for part type OBJECT. Cannot find property {1}", in, key); 309 m.put(key, parse(partType, schema.getProperty(key), value, bpm == null ? object() : bpm.getClassMeta())); 310 } 311 return m.getBean(); 312 } 313 314 ClassMeta<?> eType = type.isObject() ? string() : type.getValueType(); 315 if (eType == null) 316 eType = schema.getParsedType().getValueType(); 317 if (eType == null) 318 eType = string(); 319 320 try { 321 Map<String,Object> m = (Map<String,Object>)type.newInstance(); 322 if (m == null) 323 m = OMap.of(); 324 325 for (String s : ss) { 326 String[] kv = split(s, '=', 2); 327 if (kv.length != 2) 328 throw new ParseException("Invalid input {0} for part type OBJECT.", in); 329 String key = kv[0], value = kv[1]; 330 m.put(key, parse(partType, schema.getProperty(key), value, eType)); 331 } 332 return (T)m; 333 } catch (ExecutableException e) { 334 throw new ParseException(e); 335 } 336 337 } else if (t == FILE) { 338 throw new ParseException("File part not supported."); 339 340 } else if (t == NO_TYPE) { 341 // This should never be returned by HttpPartSchema.getType(ClassMeta). 342 throw new ParseException("Invalid type."); 343 } 344 } 345 346 return super.parse(partType, schema, in, type); 347 } 348 349 private <T> T toType(Object in, ClassMeta<T> type) throws ParseException { 350 try { 351 return convertToType(in, type); 352 } catch (InvalidDataConversionException e) { 353 throw new ParseException(e.getMessage()); 354 } 355 } 356 357 //----------------------------------------------------------------------------------------------------------------- 358 // Other methods 359 //----------------------------------------------------------------------------------------------------------------- 360 361 @Override /* Session */ 362 public OMap toMap() { 363 return super.toMap() 364 .a("OpenApiParserSession", new DefaultFilteringOMap() 365 ); 366 } 367}