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.CollectionUtils.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.httppart.HttpPartCollectionFormat.*;
018import static org.apache.juneau.httppart.HttpPartDataType.*;
019import static org.apache.juneau.httppart.HttpPartFormat.*;
020
021import java.io.*;
022import java.lang.reflect.*;
023import java.nio.charset.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.collections.*;
029import org.apache.juneau.httppart.*;
030import org.apache.juneau.internal.*;
031import org.apache.juneau.parser.*;
032import org.apache.juneau.swap.*;
033import org.apache.juneau.swaps.*;
034import org.apache.juneau.uon.*;
035
036/**
037 * Session object that lives for the duration of a single use of {@link OpenApiParser}.
038 *
039 * <h5 class='section'>Notes:</h5><ul>
040 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
041 * </ul>
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.OpenApiDetails">OpenAPI Details</a>
045
046 * </ul>
047 */
048public class OpenApiParserSession extends UonParserSession {
049
050   //-------------------------------------------------------------------------------------------------------------------
051   // Static
052   //-------------------------------------------------------------------------------------------------------------------
053
054   // Cache these for faster lookup
055   private static final BeanContext BC = BeanContext.DEFAULT;
056   private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class);
057   private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class);
058   private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class);
059   private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class);
060   private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class);
061   private static final ClassMeta<JsonList> CM_JsonList = BC.getClassMeta(JsonList.class);
062   private static final ClassMeta<JsonMap> CM_JsonMap = BC.getClassMeta(JsonMap.class);
063
064   private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT;
065
066   /**
067    * Creates a new builder for this object.
068    *
069    * @param ctx The context creating this session.
070    * @return A new builder.
071    */
072   public static Builder create(OpenApiParser ctx) {
073      return new Builder(ctx);
074   }
075
076   //-------------------------------------------------------------------------------------------------------------------
077   // Builder
078   //-------------------------------------------------------------------------------------------------------------------
079
080   /**
081    * Builder class.
082    */
083   @FluentSetters
084   public static class Builder extends UonParserSession.Builder {
085
086      OpenApiParser ctx;
087
088      /**
089       * Constructor
090       *
091       * @param ctx The context creating this session.
092       */
093      protected Builder(OpenApiParser ctx) {
094         super(ctx);
095         this.ctx = ctx;
096      }
097
098      @Override
099      public OpenApiParserSession build() {
100         return new OpenApiParserSession(this);
101      }
102
103      // <FluentSetters>
104
105      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
106      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
107         super.apply(type, apply);
108         return this;
109      }
110
111      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
112      public Builder debug(Boolean value) {
113         super.debug(value);
114         return this;
115      }
116
117      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
118      public Builder properties(Map<String,Object> value) {
119         super.properties(value);
120         return this;
121      }
122
123      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
124      public Builder property(String key, Object value) {
125         super.property(key, value);
126         return this;
127      }
128
129      @Override /* GENERATED - org.apache.juneau.ContextSession.Builder */
130      public Builder unmodifiable() {
131         super.unmodifiable();
132         return this;
133      }
134
135      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
136      public Builder locale(Locale value) {
137         super.locale(value);
138         return this;
139      }
140
141      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
142      public Builder localeDefault(Locale value) {
143         super.localeDefault(value);
144         return this;
145      }
146
147      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
148      public Builder mediaType(MediaType value) {
149         super.mediaType(value);
150         return this;
151      }
152
153      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
154      public Builder mediaTypeDefault(MediaType value) {
155         super.mediaTypeDefault(value);
156         return this;
157      }
158
159      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
160      public Builder timeZone(TimeZone value) {
161         super.timeZone(value);
162         return this;
163      }
164
165      @Override /* GENERATED - org.apache.juneau.BeanSession.Builder */
166      public Builder timeZoneDefault(TimeZone value) {
167         super.timeZoneDefault(value);
168         return this;
169      }
170
171      @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */
172      public Builder javaMethod(Method value) {
173         super.javaMethod(value);
174         return this;
175      }
176
177      @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */
178      public Builder outer(Object value) {
179         super.outer(value);
180         return this;
181      }
182
183      @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */
184      public Builder schema(HttpPartSchema value) {
185         super.schema(value);
186         return this;
187      }
188
189      @Override /* GENERATED - org.apache.juneau.parser.ParserSession.Builder */
190      public Builder schemaDefault(HttpPartSchema value) {
191         super.schemaDefault(value);
192         return this;
193      }
194
195      @Override /* GENERATED - org.apache.juneau.parser.ReaderParserSession.Builder */
196      public Builder fileCharset(Charset value) {
197         super.fileCharset(value);
198         return this;
199      }
200
201      @Override /* GENERATED - org.apache.juneau.parser.ReaderParserSession.Builder */
202      public Builder streamCharset(Charset value) {
203         super.streamCharset(value);
204         return this;
205      }
206
207      @Override /* GENERATED - org.apache.juneau.uon.UonParserSession.Builder */
208      public Builder decoding(boolean value) {
209         super.decoding(value);
210         return this;
211      }
212
213      // </FluentSetters>
214   }
215
216   //-------------------------------------------------------------------------------------------------------------------
217   // Instance
218   //-------------------------------------------------------------------------------------------------------------------
219
220   private final OpenApiParser ctx;
221
222   /**
223    * Constructor.
224    *
225    * @param builder The builder for this object.
226    */
227   protected OpenApiParserSession(Builder builder) {
228      super(builder);
229      ctx = builder.ctx;
230   }
231
232
233   @SuppressWarnings("unchecked")
234   @Override /* HttpPartParser */
235   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws ParseException, SchemaValidationException {
236      if (partType == null)
237         partType = HttpPartType.OTHER;
238
239      boolean isOptional = type.isOptional();
240
241      while (type != null && type.isOptional())
242         type = (ClassMeta<T>)type.getElementType();
243
244      if (type == null)
245         type = (ClassMeta<T>)object();
246
247      schema = ObjectUtils.firstNonNull(schema, getSchema(), DEFAULT_SCHEMA);
248
249      T t = parseInner(partType, schema, in, type);
250      if (t == null && type.isPrimitive())
251         t = type.getPrimitiveDefault();
252      schema.validateOutput(t, ctx.getBeanContext());
253
254      if (isOptional)
255         t = (T)optional(t);
256
257      return t;
258   }
259
260   @Override /* ParserSession */
261   protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException {
262      return parseInner(null, HttpPartSchema.DEFAULT, pipe.asString(), type);
263   }
264
265   @SuppressWarnings({ "unchecked" })
266   private<T> T parseInner(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> type) throws SchemaValidationException, ParseException {
267      schema.validateInput(in);
268      if (in == null || "null".equals(in)) {
269         if (schema.getDefault() == null)
270            return null;
271         in = schema.getDefault();
272      } else {
273
274         ObjectSwap<T,Object> swap = (ObjectSwap<T,Object>)type.getSwap(this);
275         BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)type.getBuilderSwap(this);
276         ClassMeta<?> sType = null;
277         if (builder != null)
278            sType = builder.getBuilderClassMeta(this);
279         else if (swap != null)
280            sType = swap.getSwapClassMeta(this);
281         else
282            sType = type;
283
284         if (sType.isOptional())
285            return (T)optional(parseInner(partType, schema, in, sType.getElementType()));
286
287         HttpPartDataType t = schema.getType(sType);
288         if (partType == null)
289            partType = HttpPartType.OTHER;
290
291         HttpPartFormat f = schema.getFormat(sType);
292         if (f == HttpPartFormat.NO_FORMAT)
293            f = ctx.getFormat();
294
295         if (t == STRING) {
296            if (sType.isObject()) {
297               if (f == BYTE)
298                  return toType(base64Decode(in), type);
299               if (f == DATE || f == DATE_TIME)
300                  return toType(parseIsoCalendar(in), type);
301               if (f == BINARY)
302                  return toType(fromHex(in), type);
303               if (f == BINARY_SPACED)
304                  return toType(fromSpacedHex(in), type);
305               if (f == HttpPartFormat.UON)
306                  return super.parse(partType, schema, in, type);
307               return toType(in, type);
308            }
309            if (f == BYTE)
310               return toType(base64Decode(in), type);
311            if (f == DATE) {
312               try {
313                  if (type.isCalendar())
314                     return toType(TemporalCalendarSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
315                  if (type.isDate())
316                     return toType(TemporalDateSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
317                  if (type.isTemporal())
318                     return toType(TemporalSwap.IsoDate.DEFAULT.unswap(this, in, type), type);
319                  return toType(in, type);
320               } catch (Exception e) {
321                  throw new ParseException(e);
322               }
323            }
324            if (f == DATE_TIME) {
325               try {
326                  if (type.isCalendar())
327                     return toType(TemporalCalendarSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
328                  if (type.isDate())
329                     return toType(TemporalDateSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
330                  if (type.isTemporal())
331                     return toType(TemporalSwap.IsoDateTime.DEFAULT.unswap(this, in, type), type);
332                  return toType(in, type);
333               } catch (Exception e) {
334                  throw new ParseException(e);
335               }
336            }
337            if (f == BINARY)
338               return toType(fromHex(in), type);
339            if (f == BINARY_SPACED)
340               return toType(fromSpacedHex(in), type);
341            if (f == HttpPartFormat.UON)
342               return super.parse(partType, schema, in, type);
343            return toType(in, type);
344
345         } else if (t == BOOLEAN) {
346            if (type.isObject())
347               type = (ClassMeta<T>)CM_Boolean;
348            if (type.isBoolean())
349               return super.parse(partType, schema, in, type);
350            return toType(super.parse(partType, schema, in, CM_Boolean), type);
351
352         } else if (t == INTEGER) {
353            if (type.isObject()) {
354               if (f == INT64)
355                  type = (ClassMeta<T>)CM_Long;
356               else
357                  type = (ClassMeta<T>)CM_Integer;
358            }
359            if (type.isNumber())
360               return super.parse(partType, schema, in, type);
361            return toType(super.parse(partType, schema, in, CM_Integer), type);
362
363         } else if (t == NUMBER) {
364            if (type.isObject()) {
365               if (f == DOUBLE)
366                  type = (ClassMeta<T>)CM_Double;
367               else
368                  type = (ClassMeta<T>)CM_Float;
369            }
370            if (type.isNumber())
371               return super.parse(partType, schema, in, type);
372            return toType(super.parse(partType, schema, in, CM_Double), type);
373
374         } else if (t == ARRAY) {
375
376            HttpPartCollectionFormat cf = schema.getCollectionFormat();
377            if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT)
378               cf = ctx.getCollectionFormat();
379
380            if (cf == HttpPartCollectionFormat.UONC)
381               return super.parse(partType, schema, in, type);
382
383            if (type.isObject())
384               type = (ClassMeta<T>)CM_JsonList;
385
386            ClassMeta<?> eType = type.isObject() ? string() : type.getElementType();
387            if (eType == null)
388               eType = schema.getParsedType().getElementType();
389            if (eType == null)
390               eType = string();
391
392            String[] ss = {};
393
394            if (cf == MULTI)
395               ss = new String[]{in};
396            else if (cf == CSV)
397               ss = split(in, ',');
398            else if (cf == PIPES)
399               ss = split(in, '|');
400            else if (cf == SSV)
401               ss = splitQuoted(in);
402            else if (cf == TSV)
403               ss = split(in, '\t');
404            else if (cf == HttpPartCollectionFormat.UONC)
405               return super.parse(partType, null, in, type);
406            else if (cf == NO_COLLECTION_FORMAT) {
407               if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')')
408                  return super.parse(partType, null, in, type);
409               ss = split(in, ',');
410            }
411
412            HttpPartSchema items = schema.getItems();
413            if (items == null)
414               items = HttpPartSchema.DEFAULT;
415            Object o = Array.newInstance(eType.getInnerClass(), ss.length);
416            for (int i = 0; i < ss.length; i++)
417               Array.set(o, i, parse(partType, items, ss[i], eType));
418            if (type.hasMutaterFrom(schema.getParsedType()) || schema.getParsedType().hasMutaterTo(type))
419               return toType(toType(o, schema.getParsedType()), type);
420            return toType(o, type);
421
422         } else if (t == OBJECT) {
423
424            HttpPartCollectionFormat cf = schema.getCollectionFormat();
425            if (cf == HttpPartCollectionFormat.NO_COLLECTION_FORMAT)
426               cf = ctx.getCollectionFormat();
427
428            if (cf == HttpPartCollectionFormat.UONC)
429               return super.parse(partType, schema, in, type);
430
431            if (type.isObject())
432               type = (ClassMeta<T>)CM_JsonMap;
433
434            if (! type.isMapOrBean())
435               throw new ParseException("Invalid type {0} for part type OBJECT.", type);
436
437            String[] ss = {};
438
439            if (cf == MULTI)
440               ss = new String[]{in};
441            else if (cf == CSV)
442               ss = split(in, ',');
443            else if (cf == PIPES)
444               ss = split(in, '|');
445            else if (cf == SSV)
446               ss = splitQuoted(in);
447            else if (cf == TSV)
448               ss = split(in, '\t');
449            else if (cf == HttpPartCollectionFormat.UONC)
450               return super.parse(partType, null, in, type);
451            else if (cf == NO_COLLECTION_FORMAT) {
452               if (firstNonWhitespaceChar(in) == '@' && lastNonWhitespaceChar(in) == ')')
453                  return super.parse(partType, null, in, type);
454               ss = split(in, ',');
455            }
456
457            if (type.isBean()) {
458               BeanMap<T> m = ctx.getBeanContext().newBeanMap(type.getInnerClass());
459               for (String s : ss) {
460                  String[] kv = split(s, '=', 2);
461                  if (kv.length != 2)
462                     throw new ParseException("Invalid input {0} for part type OBJECT.", in);
463                  String key = kv[0], value = kv[1];
464                  BeanPropertyMeta bpm = m.getPropertyMeta(key);
465                  if (bpm == null && ! isIgnoreUnknownBeanProperties())
466                     throw new ParseException("Invalid input {0} for part type OBJECT.  Cannot find property {1}", in, key);
467                  m.put(key, parse(partType, schema.getProperty(key), value, ((ClassMeta<T>)(bpm == null ? object() : bpm.getClassMeta()))));
468               }
469               return m.getBean();
470            }
471
472            ClassMeta<?> eType = type.isObject() ? string() : type.getValueType();
473            if (eType == null)
474               eType = schema.getParsedType().getValueType();
475            if (eType == null)
476               eType = string();
477
478            try {
479               Map<String,Object> m = (Map<String,Object>)type.newInstance();
480               if (m == null)
481                  m = JsonMap.create();
482
483               for (String s : ss) {
484                  String[] kv = split(s, '=', 2);
485                  if (kv.length != 2)
486                     throw new ParseException("Invalid input {0} for part type OBJECT.", in);
487                  String key = kv[0], value = kv[1];
488                  m.put(key, parse(partType, schema.getProperty(key), value, eType));
489               }
490               return (T)m;
491            } catch (ExecutableException e) {
492               throw new ParseException(e);
493            }
494
495         } else if (t == FILE) {
496            throw new ParseException("File part not supported.");
497
498         } else if (t == NO_TYPE) {
499            // This should never be returned by HttpPartSchema.getType(ClassMeta).
500            throw new ParseException("Invalid type.");
501         }
502      }
503
504      return super.parse(partType, schema, in, type);
505   }
506
507   private <T> T toType(Object in, ClassMeta<T> type) throws ParseException {
508      try {
509         return convertToType(in, type);
510      } catch (InvalidDataConversionException e) {
511         throw new ParseException(e.getMessage());
512      }
513   }
514}