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 * Serializes POJOs to values suitable for transmission as HTTP headers, query/form-data parameters, and path variables.
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 OpenApiSerializer hierarchy
043   "java:S9149" // Per-format static factories intentionally shadow the parent's.
044})
045public class OpenApiSerializer extends UonSerializer implements OpenApiMetaProvider {
046
047   /**
048    * Builder class.
049    */
050   public static class Builder extends UonSerializer.Builder<Builder> {
051
052      private static final Cache<HashKey,OpenApiSerializer> CACHE = Cache.of(HashKey.class, OpenApiSerializer.class).build();
053
054      HttpPartFormat format;
055      HttpPartCollectionFormat collectionFormat;
056
057      /**
058       * Constructor, default settings.
059       */
060      protected Builder() {
061         produces("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(OpenApiSerializer copyFrom) {
083         super(copyFrom);
084         format = copyFrom.format;
085         collectionFormat = copyFrom.collectionFormat;
086      }
087
088      @Override /* Overridden from Context.Builder<?> */
089      public OpenApiSerializer build() {
090         return cache(CACHE).build(OpenApiSerializer.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 serializer using CSV for collections.</jc>
102       *    OpenApiSerializer <jv>serializer1</jv> = OpenApiSerializer
103       *       .<jsm>create</jsm>()
104       *       .collectionFormat(<jsf>CSV</jsf>)
105       *       .build();
106       *
107       *    <jc>// Create a serializer using UON for collections.</jc>
108       *    OpenApiSerializer <jv>serializer2</jv> = OpenApiSerializer
109       *       .<jsm>create</jsm>()
110       *       .collectionFormat(<jsf>UON</jsf>)
111       *       .build();
112       *
113       *    <jc>// An arbitrary data structure.</jc>
114       *    JsonList <jv>list</jv> = JsonList.<jsm>of</jsm>(
115       *       <js>"foo"</js>,
116       *       <js>"bar"</js>,
117       *       JsonMap.<jsm>of</jsm>(
118       *          <js>"baz"</js>, JsonList.<jsm>of</jsm>(<js>"qux"</js>,<js>"true"</js>,<js>"123"</js>)
119       *    )
120       * );
121       *
122       *    <jc>// Produces: "foo=bar,baz=qux\,true\,123"</jc>
123       *    String <jv>value1</jv> = <jv>serializer1</jv>.write(<jv>list</jv>)
124       *
125       *    <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc>
126       *    String <jv>value2</jv> = <jv>serializer2</jv>.write(<jv>list</jv>)
127       * </p>
128       *
129       * <ul class='values javatree'>
130       *    <li class='jc'>{@link HttpPartFormat}
131       *    <ul>
132       *       <li class='jf'>{@link HttpPartCollectionFormat#CSV CSV} - (default) Comma-separated values (e.g. <js>"foo,bar"</js>).
133       *       <li class='jf'>{@link HttpPartCollectionFormat#SSV SSV} - Space-separated values (e.g. <js>"foo bar"</js>).
134       *       <li class='jf'>{@link HttpPartCollectionFormat#TSV TSV} - Tab-separated values (e.g. <js>"foo\tbar"</js>).
135       *       <li class='jf'>{@link HttpPartCollectionFormat#PIPES PIPES} - Pipe-separated values (e.g. <js>"foo|bar"</js>).
136       *       <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>).
137       *       <li class='jf'>{@link HttpPartCollectionFormat#UONC UONC} - UON collection notation (e.g. <js>"@(foo,bar)"</js>).
138       *    </ul>
139       * </ul>
140       *
141       * @param value The new value for this property.
142       * @return This object.
143       */
144      public Builder collectionFormat(HttpPartCollectionFormat value) {
145         collectionFormat = value;
146         return this;
147      }
148
149      @Override /* Overridden from Context.Builder<?> */
150      public Builder copy() {
151         return new Builder(this);
152      }
153
154      /**
155       * <i><l>OpenApiCommon</l> configuration property:&emsp;</i>  Default format for HTTP parts.
156       *
157       * <p>
158       * Specifies the format to use for HTTP parts when not otherwise specified via {@link Schema#format()}.
159       *
160       * <h5 class='section'>Example:</h5>
161       * <p class='bjava'>
162       *    <jc>// Create a plain-text serializer.</jc>
163       *    OpenApiSerializer <jv>serializer1</jv> = OpenApiSerializer
164       *       .<jsm>create</jsm>()
165       *       .build();
166       *
167       *    <jc>// Create a UON serializer.</jc>
168       *    OpenApiSerializer <jv>serializer2</jv> = OpenApiSerializer
169       *       .<jsm>create</jsm>()
170       *       .format(<jsf>UON</jsf>)
171       *       .build();
172       *
173       *    String <jv>string</jv> = <js>"foo bar"</js>;
174       *
175       *    <jc>// Produces: "foo bar"</jc>
176       *    String <jv>value1</jv> = <jv>serializer1</jv>.write(<jv>string</jv>);
177       *
178       *    <jc>// Produces: "'foo bar'"</jc>
179       *    String <jv>value2</jv> = <jv>serializer2</jv>.write(<jv>string</jv>);
180       * </p>
181       *
182       * <ul class='values javatree'>
183       *    <li class='jc'>{@link HttpPartFormat}
184       *    <ul>
185       *       <li class='jf'>{@link HttpPartFormat#UON UON} - UON notation (e.g. <js>"'foo bar'"</js>).
186       *       <li class='jf'>{@link HttpPartFormat#INT32 INT32} - Signed 32 bits.
187       *       <li class='jf'>{@link HttpPartFormat#INT64 INT64} - Signed 64 bits.
188       *       <li class='jf'>{@link HttpPartFormat#FLOAT FLOAT} - 32-bit floating point number.
189       *       <li class='jf'>{@link HttpPartFormat#DOUBLE DOUBLE} - 64-bit floating point number.
190       *       <li class='jf'>{@link HttpPartFormat#BYTE BYTE} - BASE-64 encoded characters.
191       *       <li class='jf'>{@link HttpPartFormat#BINARY BINARY} - Hexadecimal encoded octets (e.g. <js>"00FF"</js>).
192       *       <li class='jf'>{@link HttpPartFormat#BINARY_SPACED BINARY_SPACED} - Spaced-separated hexadecimal encoded octets (e.g. <js>"00 FF"</js>).
193       *       <li class='jf'>{@link HttpPartFormat#DATE DATE} - An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>.
194       *       <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>.
195       *       <li class='jf'>{@link HttpPartFormat#PASSWORD PASSWORD} - Used to hint UIs the input needs to be obscured.
196       *       <li class='jf'>{@link HttpPartFormat#NO_FORMAT NO_FORMAT} - (default) Not specified.
197       *    </ul>
198       * </ul>
199       *
200       * @param value The new value for this property.
201       * @return This object.
202       */
203      public Builder format(HttpPartFormat value) {
204         format = value;
205         return this;
206      }
207
208      @Override /* Overridden from Context.Builder<?> */
209      public HashKey hashKey() {
210         // @formatter:off
211         return HashKey.of(
212            super.hashKey(),
213            format,
214            collectionFormat
215         );
216         // @formatter:on
217      }
218
219
220   }
221
222   /** Reusable instance of {@link OpenApiSerializer}, all default settings. */
223   public static final OpenApiSerializer DEFAULT = new OpenApiSerializer(create());
224
225   /**
226    * Creates a new builder for this object.
227    *
228    * @return A new builder.
229    */
230   public static Builder create() {
231      return new Builder();
232   }
233
234   final HttpPartFormat format;
235   final HttpPartCollectionFormat collectionFormat;
236
237   private final Map<ClassMeta<?>,OpenApiClassMeta> openApiClassMetas = new ConcurrentHashMap<>();
238   private final Map<BeanPropertyMeta,OpenApiBeanPropertyMeta> openApiBeanPropertyMetas = new ConcurrentHashMap<>();
239
240   /**
241    * Constructor.
242    *
243    * @param builder
244    *    The builder for this object.
245    */
246   public OpenApiSerializer(Builder builder) {
247      super(builder.encoding(false));
248      format = builder.format;
249      collectionFormat = builder.collectionFormat;
250   }
251
252   @Override /* Overridden from Context */
253   public Builder copy() {
254      return new Builder(this);
255   }
256
257   @Override /* Overridden from Context */
258   public OpenApiSerializerSession.Builder createSession() {
259      return OpenApiSerializerSession.create(this);
260   }
261
262   @Override /* Overridden from OpenApiMetaProvider */
263   public OpenApiBeanPropertyMeta getOpenApiBeanPropertyMeta(BeanPropertyMeta bpm) {
264      if (bpm == null)
265         return OpenApiBeanPropertyMeta.DEFAULT;
266      return openApiBeanPropertyMetas.computeIfAbsent(bpm, k -> new OpenApiBeanPropertyMeta(k.getDelegateFor(), this));
267   }
268
269   @Override /* Overridden from OpenApiMetaProvider */
270   public OpenApiClassMeta getOpenApiClassMeta(ClassMeta<?> cm) {
271      return openApiClassMetas.computeIfAbsent(cm, k -> new OpenApiClassMeta(k, this));
272   }
273
274   @Override /* Overridden from HttpPartSerializer */
275   public OpenApiSerializerSession getPartSession() { return OpenApiSerializerSession.create(this).build(); }
276
277   @Override /* Overridden from Context */
278   public OpenApiSerializerSession getSession() { return createSession().build(); }
279
280   /**
281    * Returns the default collection format to use when not otherwise specified via {@link Schema#collectionFormat()}
282    *
283    * @return The default collection format to use when not otherwise specified via {@link Schema#collectionFormat()}
284    */
285   protected final HttpPartCollectionFormat getCollectionFormat() { return collectionFormat; }
286
287   /**
288    * Returns the default format to use when not otherwise specified via {@link Schema#format()}
289    *
290    * @return The default format to use when not otherwise specified via {@link Schema#format()}
291    */
292   protected final HttpPartFormat getFormat() { return format; }
293}