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}