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.HttpPartSchema.Type.*; 017import static org.apache.juneau.httppart.HttpPartSchema.Format.*; 018import static org.apache.juneau.httppart.HttpPartSchema.CollectionFormat.*; 019 020import java.util.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.httppart.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.parser.*; 026import org.apache.juneau.uon.*; 027 028/** 029 * Session object that lives for the duration of a single use of {@link OpenApiParser}. 030 * 031 * <p> 032 * This class is NOT thread safe. 033 * It is typically discarded after one-time use although it can be reused within the same thread. 034 */ 035public class OpenApiParserSession extends UonParserSession { 036 037 // Cache these for faster lookup 038 private static final BeanContext BC = BeanContext.DEFAULT; 039 private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class); 040 private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class); 041 private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class); 042 private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class); 043 private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class); 044 private static final ClassMeta<ObjectList> CM_ObjectList = BC.getClassMeta(ObjectList.class); 045 private static final ClassMeta<ObjectMap> CM_ObjectMap = BC.getClassMeta(ObjectMap.class); 046 047 private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT; 048 049 //------------------------------------------------------------------------------------------------------------------- 050 // Instance 051 //------------------------------------------------------------------------------------------------------------------- 052 053 private final OpenApiParser ctx; 054 055 /** 056 * Create a new session using properties specified in the context. 057 * 058 * @param ctx 059 * The context creating this session object. 060 * The context contains all the configuration settings for this object. 061 * @param args 062 * Runtime session arguments. 063 */ 064 protected OpenApiParserSession(OpenApiParser ctx, ParserSessionArgs args) { 065 super(ctx, args); 066 this.ctx = ctx; 067 } 068 069 070 @SuppressWarnings("unchecked") 071 @Override /* HttpPartParser */ 072 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException { 073 boolean isOptional = type.isOptional(); 074 075 while (type != null && type.isOptional()) 076 type = (ClassMeta<T>)type.getElementType(); 077 078 if (type == null) 079 type = (ClassMeta<T>)object(); 080 081 schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA); 082 083 T t = parseInner(partType, schema, in, type); 084 if (t == null && type.isPrimitive()) 085 t = type.getPrimitiveDefault(); 086 schema.validateOutput(t, ctx); 087 088 if (isOptional) 089 t = (T)Optional.ofNullable(t); 090 091 return t; 092 } 093 094 @SuppressWarnings({ "unchecked" }) 095 private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException { 096 schema.validateInput(in); 097 if (in == null) { 098 if (schema.getDefault() == null) 099 return null; 100 in = schema.getDefault(); 101 } else { 102 HttpPartSchema.Type t = schema.getType(type); 103 HttpPartSchema.Format f = schema.getFormat(type); 104 105 if (t == STRING) { 106 if (type.isObject()) { 107 if (f == BYTE) 108 return (T)base64Decode(in); 109 if (f == DATE || f == DATE_TIME) 110 return (T)parseIsoCalendar(in); 111 if (f == BINARY) 112 return (T)fromHex(in); 113 if (f == BINARY_SPACED) 114 return (T)fromSpacedHex(in); 115 if (f == HttpPartSchema.Format.UON) 116 return super.parse(partType, schema, in, type); 117 return (T)in; 118 } 119 if (f == BYTE) 120 return toType(base64Decode(in), type); 121 if (f == DATE || f == DATE_TIME) 122 return toType(parseIsoCalendar(in), type); 123 if (f == BINARY) 124 return toType(fromHex(in), type); 125 if (f == BINARY_SPACED) 126 return toType(fromSpacedHex(in), type); 127 if (f == HttpPartSchema.Format.UON) 128 return super.parse(partType, schema, in, type); 129 return toType(in, type); 130 131 } else if (t == ARRAY) { 132 if (type.isObject()) 133 type = (ClassMeta<T>)CM_ObjectList; 134 135 ClassMeta<?> eType = type.isObject() ? string() : type.getElementType(); 136 if (eType == null) 137 eType = schema.getParsedType().getElementType(); 138 139 HttpPartSchema.CollectionFormat cf = schema.getCollectionFormat(); 140 String[] ss = new String[0]; 141 142 if (cf == MULTI) 143 ss = new String[]{in}; 144 else if (cf == CSV) 145 ss = split(in, ','); 146 else if (cf == PIPES) 147 ss = split(in, '|'); 148 else if (cf == SSV) 149 ss = splitQuoted(in); 150 else if (cf == TSV) 151 ss = split(in, '\t'); 152 else if (cf == HttpPartSchema.CollectionFormat.UON) 153 return super.parse(partType, null, in, type); 154 else if (cf == NO_COLLECTION_FORMAT) { 155 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 156 return super.parse(partType, null, in, type); 157 ss = split(in, ','); 158 } 159 160 HttpPartSchema items = schema.getItems(); 161 if (items == null) 162 items = HttpPartSchema.DEFAULT; 163 Object[] o = new Object[ss.length]; 164 for (int i = 0; i < ss.length; i++) 165 o[i] = parse(partType, items, ss[i], eType); 166 if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type)) 167 return toType(toType(o, schema.getParsedType()), type); 168 return toType(o, type); 169 170 } else if (t == BOOLEAN) { 171 if (type.isObject()) 172 type = (ClassMeta<T>)CM_Boolean; 173 if (type.isBoolean()) 174 return super.parse(partType, schema, in, type); 175 return toType(super.parse(partType, schema, in, CM_Boolean), type); 176 177 } else if (t == INTEGER) { 178 if (type.isObject()) { 179 if (f == INT64) 180 type = (ClassMeta<T>)CM_Long; 181 else 182 type = (ClassMeta<T>)CM_Integer; 183 } 184 if (type.isNumber()) 185 return super.parse(partType, schema, in, type); 186 return toType(super.parse(partType, schema, in, CM_Integer), type); 187 188 } else if (t == NUMBER) { 189 if (type.isObject()) { 190 if (f == DOUBLE) 191 type = (ClassMeta<T>)CM_Double; 192 else 193 type = (ClassMeta<T>)CM_Float; 194 } 195 if (type.isNumber()) 196 return super.parse(partType, schema, in, type); 197 return toType(super.parse(partType, schema, in, CM_Integer), type); 198 199 } else if (t == OBJECT) { 200 if (type.isObject()) 201 type = (ClassMeta<T>)CM_ObjectMap; 202 if (schema.hasProperties() && type.isMapOrBean()) { 203 try { 204 if (type.isBean()) { 205 BeanMap<T> m = BC.createBeanSession().newBeanMap(type.getInnerClass()); 206 for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) { 207 String key = e.getKey(); 208 BeanPropertyMeta bpm = m.getPropertyMeta(key); 209 m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), bpm == null ? object() : bpm.getClassMeta())); 210 } 211 return m.getBean(); 212 } 213 Map<String,Object> m = (Map<String,Object>)type.newInstance(); 214 for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) { 215 String key = e.getKey(); 216 m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), object())); 217 } 218 return (T)m; 219 } catch (Exception e1) { 220 throw new ParseException(e1, "Could not instantiate type ''{0}''.", type); 221 } 222 } 223 return super.parse(partType, schema, in, type); 224 225 } else if (t == FILE) { 226 throw new ParseException("File part not supported."); 227 228 } else if (t == NO_TYPE) { 229 // This should never be returned by HttpPartSchema.getType(ClassMeta). 230 throw new ParseException("Invalid type."); 231 } 232 } 233 234 return super.parse(partType, schema, in, type); 235 } 236 237 private <T> T toType(Object in, ClassMeta<T> type) throws ParseException { 238 try { 239 return convertToType(in, type); 240 } catch (InvalidDataConversionException e) { 241 throw new ParseException(e.getMessage()); 242 } 243 } 244 245 //----------------------------------------------------------------------------------------------------------------- 246 // Other methods 247 //----------------------------------------------------------------------------------------------------------------- 248 249 @Override /* Session */ 250 public ObjectMap toMap() { 251 return super.toMap() 252 .append("OpenApiParserSession", new DefaultFilteringObjectMap() 253 ); 254 } 255}