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.hasTransformFrom(schema.getParsedType()) || schema.getParsedType().hasTransformTo(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), asString(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), asString(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}