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 @Override /* HttpPartParser */ 071 public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException { 072 schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA); 073 T t = parseInner(partType, schema, in, type); 074 if (t == null && type.isPrimitive()) 075 t = type.getPrimitiveDefault(); 076 schema.validateOutput(t, ctx); 077 return t; 078 } 079 080 @SuppressWarnings({ "unchecked" }) 081 private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException { 082 schema.validateInput(in); 083 if (in == null) { 084 if (schema.getDefault() == null) 085 return null; 086 in = schema.getDefault(); 087 } else { 088 HttpPartSchema.Type t = schema.getType(type); 089 HttpPartSchema.Format f = schema.getFormat(type); 090 091 if (t == STRING) { 092 if (type.isObject()) { 093 if (f == BYTE) 094 return (T)base64Decode(in); 095 if (f == DATE || f == DATE_TIME) 096 return (T)parseIsoCalendar(in); 097 if (f == BINARY) 098 return (T)fromHex(in); 099 if (f == BINARY_SPACED) 100 return (T)fromSpacedHex(in); 101 if (f == HttpPartSchema.Format.UON) 102 return super.parse(partType, schema, in, type); 103 return (T)in; 104 } 105 if (f == BYTE) 106 return toType(base64Decode(in), type); 107 if (f == DATE || f == DATE_TIME) 108 return toType(parseIsoCalendar(in), type); 109 if (f == BINARY) 110 return toType(fromHex(in), type); 111 if (f == BINARY_SPACED) 112 return toType(fromSpacedHex(in), type); 113 if (f == HttpPartSchema.Format.UON) 114 return super.parse(partType, schema, in, type); 115 return toType(in, type); 116 117 } else if (t == ARRAY) { 118 if (type.isObject()) 119 type = (ClassMeta<T>)CM_ObjectList; 120 121 ClassMeta<?> eType = type.isObject() ? string() : type.getElementType(); 122 if (eType == null) 123 eType = schema.getParsedType().getElementType(); 124 125 HttpPartSchema.CollectionFormat cf = schema.getCollectionFormat(); 126 String[] ss = new String[0]; 127 128 if (cf == MULTI) 129 ss = new String[]{in}; 130 else if (cf == CSV) 131 ss = split(in, ','); 132 else if (cf == PIPES) 133 ss = split(in, '|'); 134 else if (cf == SSV) 135 ss = splitQuoted(in); 136 else if (cf == TSV) 137 ss = split(in, '\t'); 138 else if (cf == HttpPartSchema.CollectionFormat.UON) 139 return super.parse(partType, null, in, type); 140 else if (cf == NO_COLLECTION_FORMAT) { 141 if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')') 142 return super.parse(partType, null, in, type); 143 ss = split(in, ','); 144 } 145 146 HttpPartSchema items = schema.getItems(); 147 if (items == null) 148 items = HttpPartSchema.DEFAULT; 149 Object[] o = new Object[ss.length]; 150 for (int i = 0; i < ss.length; i++) 151 o[i] = parse(partType, items, ss[i], eType); 152 if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type)) 153 return toType(toType(o, schema.getParsedType()), type); 154 return toType(o, type); 155 156 } else if (t == BOOLEAN) { 157 if (type.isObject()) 158 type = (ClassMeta<T>)CM_Boolean; 159 if (type.isBoolean()) 160 return super.parse(partType, schema, in, type); 161 return toType(super.parse(partType, schema, in, CM_Boolean), type); 162 163 } else if (t == INTEGER) { 164 if (type.isObject()) { 165 if (f == INT64) 166 type = (ClassMeta<T>)CM_Long; 167 else 168 type = (ClassMeta<T>)CM_Integer; 169 } 170 if (type.isNumber()) 171 return super.parse(partType, schema, in, type); 172 return toType(super.parse(partType, schema, in, CM_Integer), type); 173 174 } else if (t == NUMBER) { 175 if (type.isObject()) { 176 if (f == DOUBLE) 177 type = (ClassMeta<T>)CM_Double; 178 else 179 type = (ClassMeta<T>)CM_Float; 180 } 181 if (type.isNumber()) 182 return super.parse(partType, schema, in, type); 183 return toType(super.parse(partType, schema, in, CM_Integer), type); 184 185 } else if (t == OBJECT) { 186 if (type.isObject()) 187 type = (ClassMeta<T>)CM_ObjectMap; 188 if (schema.hasProperties() && type.isMapOrBean()) { 189 try { 190 if (type.isBean()) { 191 BeanMap<T> m = BC.createBeanSession().newBeanMap(type.getInnerClass()); 192 for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) { 193 String key = e.getKey(); 194 BeanPropertyMeta bpm = m.getPropertyMeta(key); 195 m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), bpm == null ? object() : bpm.getClassMeta())); 196 } 197 return m.getBean(); 198 } 199 Map<String,Object> m = (Map<String,Object>)type.newInstance(); 200 for (Map.Entry<String,Object> e : parse(partType, DEFAULT_SCHEMA, in, CM_ObjectMap).entrySet()) { 201 String key = e.getKey(); 202 m.put(key, parse(partType, schema.getProperty(key), stringify(e.getValue()), object())); 203 } 204 return (T)m; 205 } catch (Exception e1) { 206 throw new ParseException(e1, "Could not instantiate type ''{0}''.", type); 207 } 208 } 209 return super.parse(partType, schema, in, type); 210 211 } else if (t == FILE) { 212 throw new ParseException("File part not supported."); 213 214 } else if (t == NO_TYPE) { 215 // This should never be returned by HttpPartSchema.getType(ClassMeta). 216 throw new ParseException("Invalid type."); 217 } 218 } 219 220 return super.parse(partType, schema, in, type); 221 } 222 223 private <T> T toType(Object in, ClassMeta<T> type) throws ParseException { 224 try { 225 return convertToType(in, type); 226 } catch (InvalidDataConversionException e) { 227 throw new ParseException(e.getMessage()); 228 } 229 } 230 231 //----------------------------------------------------------------------------------------------------------------- 232 // Other methods 233 //----------------------------------------------------------------------------------------------------------------- 234 235 @Override /* Session */ 236 public ObjectMap toMap() { 237 return super.toMap() 238 .append("OpenApiParserSession", new DefaultFilteringObjectMap() 239 ); 240 } 241}