001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.oapi;
018
019import java.util.*;
020import java.util.concurrent.*;
021
022import org.apache.juneau.commons.*;
023import org.apache.juneau.commons.bean.*;
024import org.apache.juneau.commons.collections.*;
025import org.apache.juneau.commons.httppart.*;
026import org.apache.juneau.marshall.*;
027import org.apache.juneau.marshall.uon.*;
028
029/**
030 * OpenAPI part parser.
031 *
032 * <h5 class='section'>Notes:</h5><ul>
033 *    <li class='note'>This class is thread safe and reusable.
034 * </ul>
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/OpenApiSupport">OpenApi</a>
038
039 * </ul>
040 */
041@SuppressWarnings({
042   "java:S110", // Inheritance depth acceptable for OpenApiParser hierarchy
043   "java:S9149" // Per-format static factories intentionally shadow the parent's.
044})
045public class OpenApiParser extends UonParser implements OpenApiMetaProvider {
046
047   /**
048    * Builder class.
049    */
050   public static class Builder extends UonParser.Builder<Builder> {
051
052      private static final Cache<HashKey,OpenApiParser> CACHE = Cache.of(HashKey.class, OpenApiParser.class).build();
053
054      HttpPartFormat format;
055      HttpPartCollectionFormat collectionFormat;
056
057      /**
058       * Constructor, default settings.
059       */
060      protected Builder() {
061         consumes("text/openapi");
062         format = HttpPartFormat.NO_FORMAT;
063         collectionFormat = HttpPartCollectionFormat.NO_COLLECTION_FORMAT;
064      }
065
066      /**
067       * Copy constructor.
068       *
069       * @param copyFrom The builder to copy from.
070       */
071      protected Builder(Builder copyFrom) {
072         super(copyFrom);
073         format = copyFrom.format;
074         collectionFormat = copyFrom.collectionFormat;
075      }
076
077      /**
078       * Copy constructor.
079       *
080       * @param copyFrom The bean to copy from.
081       */
082      protected Builder(OpenApiParser copyFrom) {
083         super(copyFrom);
084         format = copyFrom.format;
085         collectionFormat = copyFrom.collectionFormat;
086      }
087
088      @Override /* Overridden from Context.Builder<?> */
089      public OpenApiParser build() {
090         return cache(CACHE).build(OpenApiParser.class);
091      }
092
093      /**
094       * <i><l>OpenApiCommon</l> configuration property:&emsp;</i>  Default collection format for HTTP parts.
095       *
096       * <p>
097       * Specifies the collection format to use for HTTP parts when not otherwise specified via {@link Schema#collectionFormat()}.
098       *
099       * <h5 class='section'>Example:</h5>
100       * <p class='bjava'>
101       *    <jc>// Create a parser using CSV for collections.</jc>
102       *    OpenApiParser <jv>parser1</jv> = OpenApiParser
103       *       .<jsm>create</jsm>()
104       *       .collectionFormat(<jsf>CSV</jsf>)
105       *       .build();
106       *
107       *    <jc>// Create a serializer using UON for collections.</jc>
108       *    OpenApiParser <jv>parser2</jv> = OpenApiParser
109       *       .<jsm>create</jsm>()
110       *       .collectionFormat(<jsf>UON</jsf>)
111       *       .build();
112       *
113       *    <jc>// Parse CSV.</jc>
114       *    JsonList <jv>list1</jv> = <jv>parser1</jv>.read(<js>"foo=bar,baz=qux\,true\,123"</js>, JsonList.<jk>class</jk>)
115       *
116       *    <jc>// Parse UON.</jc>
117       *    JsonList <jv>list2</jv> = <jv>parser2</jv>.read(<js>"(foo=bar,baz=@(qux,true,123))"</js>, JsonList.<jk>class</jk>)
118       * </p>
119       *
120       * <ul class='values javatree'>
121       *    <li class='jc'>{@link HttpPartFormat}
122       *    <ul>
123       *       <li class='jf'>{@link HttpPartCollectionFormat#CSV CSV} - (default) Comma-separated values (e.g. <js>"foo,bar"</js>).
124       *       <li class='jf'>{@link HttpPartCollectionFormat#SSV SSV} - Space-separated values (e.g. <js>"foo bar"</js>).
125       *       <li class='jf'>{@link HttpPartCollectionFormat#TSV TSV} - Tab-separated values (e.g. <js>"foo\tbar"</js>).
126       *       <li class='jf'>{@link HttpPartCollectionFormat#PIPES PIPES} - Pipe-separated values (e.g. <js>"foo|bar"</js>).
127       *       <li class='jf'>{@link HttpPartCollectionFormat#MULTI MULTI} - Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&amp;foo=baz"</js>).
128       *       <li class='jf'>{@link HttpPartCollectionFormat#UONC UONC} - UON collection notation (e.g. <js>"@(foo,bar)"</js>).
129       *    </ul>
130       * </ul>
131       *
132       * @param value The new value for this property.
133       * @return This object.
134       */
135      public Builder collectionFormat(HttpPartCollectionFormat value) {
136         collectionFormat = value;
137         return this;
138      }
139
140      @Override /* Overridden from Context.Builder<?> */
141      public Builder copy() {
142         return new Builder(this);
143      }
144
145      /**
146       * <i><l>OpenApiCommon</l> configuration property:&emsp;</i>  Default format for HTTP parts.
147       *
148       * <p>
149       * Specifies the format to use for HTTP parts when not otherwise specified via {@link Schema#format()}.
150       *
151       * <h5 class='section'>Example:</h5>
152       * <p class='bjava'>
153       *    <jc>// Create a plain-text parser.</jc>
154       *    OpenApiParser <jv>parser1</jv> = OpenApiParser
155       *       .<jsm>create</jsm>()
156       *       .build();
157       *
158       *    <jc>// Create a UON parser.</jc>
159       *    OpenApiParser <jv>parser2</jv> = OpenApiParser
160       *       .<jsm>create</jsm>()
161       *       .format(<jsf>UON</jsf>)
162       *       .build();
163       *
164       *    <jc>// Parse a plain-text string.</jc>
165       *    String <jv>value1</jv> = <jv>parser1</jv>.read(<js>"foo bar"</js>);
166       *
167       *    <jc>// Parse a UON string.</jc>
168       *    String <jv>value2</jv> = <jv>parser1</jv>.read(<js>"'foo bar'"</js>);
169       * </p>
170       *
171       * <ul class='values javatree'>
172       *    <li class='jc'>{@link HttpPartFormat}
173       *    <ul>
174       *       <li class='jf'>{@link HttpPartFormat#UON UON} - UON notation (e.g. <js>"'foo bar'"</js>).
175       *       <li class='jf'>{@link HttpPartFormat#INT32 INT32} - Signed 32 bits.
176       *       <li class='jf'>{@link HttpPartFormat#INT64 INT64} - Signed 64 bits.
177       *       <li class='jf'>{@link HttpPartFormat#FLOAT FLOAT} - 32-bit floating point number.
178       *       <li class='jf'>{@link HttpPartFormat#DOUBLE DOUBLE} - 64-bit floating point number.
179       *       <li class='jf'>{@link HttpPartFormat#BYTE BYTE} - BASE-64 encoded characters.
180       *       <li class='jf'>{@link HttpPartFormat#BINARY BINARY} - Hexadecimal encoded octets (e.g. <js>"00FF"</js>).
181       *       <li class='jf'>{@link HttpPartFormat#BINARY_SPACED BINARY_SPACED} - Spaced-separated hexadecimal encoded octets (e.g. <js>"00 FF"</js>).
182       *       <li class='jf'>{@link HttpPartFormat#DATE DATE} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>.
183       *       <li class='jf'>{@link HttpPartFormat#DATE_TIME DATE_TIME} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>.
184       *       <li class='jf'>{@link HttpPartFormat#PASSWORD PASSWORD} - Used to hint UIs the input needs to be obscured.
185       *       <li class='jf'>{@link HttpPartFormat#NO_FORMAT NO_FORMAT} - (default) Not specified.
186       *    </ul>
187       * </ul>
188       *
189       * @param value The new value for this property.
190       * @return This object.
191       */
192      public Builder format(HttpPartFormat value) {
193         format = value;
194         return this;
195      }
196
197      @Override /* Overridden from Context.Builder<?> */
198      public HashKey hashKey() {
199         // @formatter:off
200         return HashKey.of(
201            super.hashKey(),
202            format,
203            collectionFormat
204         );
205         // @formatter:on
206      }
207
208
209   }
210
211   /** Reusable instance of {@link OpenApiParser}. */
212   public static final OpenApiParser DEFAULT = new OpenApiParser(create());
213
214   /**
215    * Creates a new builder for this object.
216    *
217    * @return A new builder.
218    */
219   public static Builder create() {
220      return new Builder();
221   }
222
223   final HttpPartFormat format;
224   final HttpPartCollectionFormat collectionFormat;
225
226   private final Map<ClassMeta<?>,OpenApiClassMeta> openApiClassMetas = new ConcurrentHashMap<>();
227   private final Map<BeanPropertyMeta,OpenApiBeanPropertyMeta> openApiBeanPropertyMetas = new ConcurrentHashMap<>();
228
229   /**
230    * Constructor.
231    *
232    * @param builder The builder for this object.
233    */
234   public OpenApiParser(Builder builder) {
235      super(builder);
236      format = builder.format;
237      collectionFormat = builder.collectionFormat;
238   }
239
240   @Override /* Overridden from Context */
241   public Builder copy() {
242      return new Builder(this);
243   }
244
245   @Override /* Overridden from Context */
246   public OpenApiParserSession.Builder createSession() {
247      return OpenApiParserSession.create(this);
248   }
249
250   @Override /* Overridden from OpenApiMetaProvider */
251   public OpenApiBeanPropertyMeta getOpenApiBeanPropertyMeta(BeanPropertyMeta bpm) {
252      if (bpm == null)
253         return OpenApiBeanPropertyMeta.DEFAULT;
254      return openApiBeanPropertyMetas.computeIfAbsent(bpm, k -> new OpenApiBeanPropertyMeta(k.getDelegateFor(), this));
255   }
256
257   @Override /* Overridden from OpenApiMetaProvider */
258   public OpenApiClassMeta getOpenApiClassMeta(ClassMeta<?> cm) {
259      return openApiClassMetas.computeIfAbsent(cm, k -> new OpenApiClassMeta(k, this));
260   }
261
262   @Override /* Overridden from HttpPartParser */
263   public OpenApiParserSession getPartSession() { return OpenApiParserSession.create(this).build(); }
264
265   @Override /* Overridden from Context */
266   public OpenApiParserSession getSession() { return createSession().build(); }
267
268   /**
269    * Returns the default collection format to use when not otherwise specified via {@link Schema#collectionFormat()}
270    *
271    * @return The default collection format to use when not otherwise specified via {@link Schema#collectionFormat()}
272    */
273   protected final HttpPartCollectionFormat getCollectionFormat() { return collectionFormat; }
274
275   /**
276    * Returns the default format to use when not otherwise specified via {@link Schema#format()}
277    *
278    * @return The default format to use when not otherwise specified via {@link Schema#format()}
279    */
280   protected final HttpPartFormat getFormat() { return format; }
281}