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.uon;
014
015import java.util.*;
016import java.util.concurrent.*;
017
018import org.apache.juneau.*;
019import org.apache.juneau.annotation.*;
020import org.apache.juneau.collections.*;
021import org.apache.juneau.httppart.*;
022import org.apache.juneau.serializer.*;
023
024/**
025 * Serializes POJO models to UON (a notation for URL-encoded query parameter values).
026 *
027 * <h5 class='topic'>Media types</h5>
028 *
029 * Handles <c>Accept</c> types:  <bc>text/uon</bc>
030 * <p>
031 * Produces <c>Content-Type</c> types:  <bc>text/uon</bc>
032 *
033 * <h5 class='topic'>Description</h5>
034 *
035 * This serializer provides several serialization options.
036 * Typically, one of the predefined DEFAULT serializers will be sufficient.
037 * However, custom serializers can be constructed to fine-tune behavior.
038 *
039 * <p>
040 * The following shows a sample object defined in Javascript:
041 * <p class='bcode w800'>
042 *    {
043 *       id: 1,
044 *       name: <js>'John Smith'</js>,
045 *       uri: <js>'http://sample/addressBook/person/1'</js>,
046 *       addressBookUri: <js>'http://sample/addressBook'</js>,
047 *       birthDate: <js>'1946-08-12T00:00:00Z'</js>,
048 *       otherIds: <jk>null</jk>,
049 *       addresses: [
050 *          {
051 *             uri: <js>'http://sample/addressBook/address/1'</js>,
052 *             personUri: <js>'http://sample/addressBook/person/1'</js>,
053 *             id: 1,
054 *             street: <js>'100 Main Street'</js>,
055 *             city: <js>'Anywhereville'</js>,
056 *             state: <js>'NY'</js>,
057 *             zip: 12345,
058 *             isCurrent: <jk>true</jk>,
059 *          }
060 *       ]
061 *    }
062 * </p>
063 *
064 * <p>
065 * Using the "strict" syntax defined in this document, the equivalent UON notation would be as follows:
066 * <p class='bcode w800'>
067 *    (
068 *       <ua>id</ua>=<un>1</un>,
069 *       <ua>name</ua>=<us>'John+Smith'</us>,
070 *       <ua>uri</ua>=<us>http://sample/addressBook/person/1</us>,
071 *       <ua>addressBookUri</ua>=<us>http://sample/addressBook</us>,
072 *       <ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>,
073 *       <ua>otherIds</ua>=<uk>null</uk>,
074 *       <ua>addresses</ua>=@(
075 *          (
076 *             <ua>uri</ua>=<us>http://sample/addressBook/address/1</us>,
077 *             <ua>personUri</ua>=<us>http://sample/addressBook/person/1</us>,
078 *             <ua>id</ua>=<un>1</un>,
079 *             <ua>street</ua>=<us>'100+Main+Street'</us>,
080 *             <ua>city</ua>=<us>Anywhereville</us>,
081 *             <ua>state</ua>=<us>NY</us>,
082 *             <ua>zip</ua>=<un>12345</un>,
083 *             <ua>isCurrent</ua>=<uk>true</uk>
084 *          )
085 *       )
086 *    )
087 * </p>
088 *
089 * <h5 class='section'>Example:</h5>
090 * <p class='bcode w800'>
091 *    <jc>// Serialize a Map</jc>
092 *    Map m = OMap.<jsm>ofJson</jsm>(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
093 *
094 *    <jc>// Serialize to value equivalent to JSON.</jc>
095 *    <jc>// Produces "(a=b,c=1,d=false,e=@(f,1,false),g=(h=i))"</jc>
096 *    String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s);
097 *
098 *    <jc>// Serialize a bean</jc>
099 *    <jk>public class</jk> Person {
100 *       <jk>public</jk> Person(String s);
101 *       <jk>public</jk> String getName();
102 *       <jk>public int</jk> getAge();
103 *       <jk>public</jk> Address getAddress();
104 *       <jk>public boolean</jk> deceased;
105 *    }
106 *
107 *    <jk>public class</jk> Address {
108 *       <jk>public</jk> String getStreet();
109 *       <jk>public</jk> String getCity();
110 *       <jk>public</jk> String getState();
111 *       <jk>public int</jk> getZip();
112 *    }
113 *
114 *    Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>,
115 *       <js>"NY"</js>, 12345, <jk>false</jk>);
116 *
117 *    <jc>// Produces "(name='John Doe',age=23,address=(street='123 Main St',city=Anywhere,state=NY,zip=12345),deceased=false)"</jc>
118 *    String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s);
119 * </p>
120 */
121@ConfigurableContext
122public class UonSerializer extends WriterSerializer implements HttpPartSerializer, UonMetaProvider, UonCommon {
123
124   //-------------------------------------------------------------------------------------------------------------------
125   // Configurable properties
126   //-------------------------------------------------------------------------------------------------------------------
127
128   static final String PREFIX = "UonSerializer";
129
130   /**
131    * Configuration property:  Add <js>"_type"</js> properties when needed.
132    *
133    * <h5 class='section'>Property:</h5>
134    * <ul class='spaced-list'>
135    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonSerializer#UON_addBeanTypes UON_addBeanTypes}
136    *    <li><b>Name:</b>  <js>"UonSerializer.addBeanTypes.b"</js>
137    *    <li><b>Data type:</b>  <jk>boolean</jk>
138    *    <li><b>System property:</b>  <c>UonSerializer.addBeanTypes</c>
139    *    <li><b>Environment variable:</b>  <c>UONSERIALIZER_ADDBEANTYPES</c>
140    *    <li><b>Default:</b>  <jk>false</jk>
141    *    <li><b>Session property:</b>  <jk>false</jk>
142    *    <li><b>Annotations:</b>
143    *       <ul>
144    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#addBeanTypes()}
145    *       </ul>
146    *    <li><b>Methods:</b>
147    *       <ul>
148    *          <li class='jm'>{@link org.apache.juneau.uon.UonSerializerBuilder#addBeanTypes()}
149    *       </ul>
150    * </ul>
151    *
152    * <h5 class='section'>Description:</h5>
153    * <p>
154    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
155    * through reflection.
156    *
157    * <p>
158    * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
159    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
160    */
161   public static final String UON_addBeanTypes = PREFIX + ".addBeanTypes.b";
162
163   /**
164    * Configuration property:  Encode non-valid URI characters.
165    *
166    * <h5 class='section'>Property:</h5>
167    * <ul class='spaced-list'>
168    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonSerializer#UON_encoding UON_encoding}
169    *    <li><b>Name:</b>  <js>"UonSerializer.encoding.b"</js>
170    *    <li><b>Data type:</b>  <jk>boolean</jk>
171    *    <li><b>System property:</b>  <c>UonSerializer.encoding</c>
172    *    <li><b>Environment variable:</b>  <c>UONSERIALIZER_ENCODING</c>
173    *    <li><b>Default:</b>  <jk>false</jk> for {@link org.apache.juneau.uon.UonSerializer}, <jk>true</jk> for {@link org.apache.juneau.urlencoding.UrlEncodingSerializer}
174    *    <li><b>Session property:</b>  <jk>false</jk>
175    *    <li><b>Annotations:</b>
176    *       <ul>
177    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#encoding()}
178    *       </ul>
179    *    <li><b>Methods:</b>
180    *       <ul>
181    *          <li class='jm'>{@link org.apache.juneau.uon.UonSerializerBuilder#encoding(boolean)}
182    *          <li class='jm'>{@link org.apache.juneau.uon.UonSerializerBuilder#encoding()}
183    *       </ul>
184    * </ul>
185    *
186    * <h5 class='section'>Description:</h5>
187    *
188    * <p>
189    * Encode non-valid URI characters with <js>"%xx"</js> constructs.
190    *
191    * <p>
192    * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences.
193    * <br>Set to <jk>false</jk> if parameter value is being passed to some other code that will already perform
194    * URL-encoding of non-valid URI characters.
195    *
196    * <h5 class='section'>Example:</h5>
197    * <p class='bcode w800'>
198    *    <jc>// Create a non-encoding UON serializer.</jc>
199    *    UonSerializer s1 = UonSerializer.
200    *       .<jsm>create</jsm>()
201    *       .build();
202    *
203    *    <jc>// Create an encoding UON serializer.</jc>
204    *    UonSerializer s2 = UonSerializer.
205    *       .<jsm>create</jsm>()
206    *       .encoding()
207    *       .build();
208    *
209    *    OMap m = OMap.<jsm>of</jsm>(<js>"foo"</js>, <js>"foo bar"</js>);
210    *
211    *    <jc>// Produces: "(foo=foo bar)"</jc>
212    *    String uon1 = s1.serialize(m)
213    *
214    *    <jc>// Produces: "(foo=foo%20bar)"</jc>
215    *    String uon2 = s2.serialize(m)
216    * </p>
217    */
218   public static final String UON_encoding = PREFIX + ".encoding.b";
219
220   /**
221    * Configuration property:  Format to use for query/form-data/header values.
222    *
223    * <h5 class='section'>Property:</h5>
224    * <ul class='spaced-list'>
225    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonSerializer#UON_paramFormat UON_paramFormat}
226    *    <li><b>Name:</b>  <js>"UonSerializer.paramFormat.s"</js>
227    *    <li><b>Data type:</b>  {@link org.apache.juneau.uon.ParamFormat}
228    *    <li><b>System property:</b>  <c>UonSerializer.paramFormat</c>
229    *    <li><b>Environment variable:</b>  <c>UONSERIALIZER_PARAMFORMAT</c>
230    *    <li><b>Default:</b>  <js>"UON"</js>
231    *    <li><b>Session property:</b>  <jk>false</jk>
232    *    <li><b>Annotations:</b>
233    *       <ul>
234    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#paramFormat()}
235    *       </ul>
236    *    <li><b>Methods:</b>
237    *       <ul>
238    *          <li class='jm'>{@link org.apache.juneau.uon.UonSerializerBuilder#paramFormat(ParamFormat)}
239    *          <li class='jm'>{@link org.apache.juneau.uon.UonSerializerBuilder#paramFormatPlain()}
240    *       </ul>
241    * </ul>
242    *
243    * <h5 class='section'>Description:</h5>
244    *
245    * <p>
246    * Specifies the format to use for URL GET parameter keys and values.
247    *
248    * <p>
249    * Possible values:
250    * <ul class='javatree'>
251    *    <li class='jf'>{@link ParamFormat#UON} (default) - Use UON notation for parameters.
252    *    <li class='jf'>{@link ParamFormat#PLAINTEXT} - Use plain text for parameters.
253    * </ul>
254    *
255    * <h5 class='section'>Example:</h5>
256    * <p class='bcode w800'>
257    *    <jc>// Create a normal UON serializer.</jc>
258    *    UonSerializer s1 = UonSerializer
259    *       .<jsm>create</jsm>()
260    *       .build();
261    *
262    *    <jc>// Create a plain-text UON serializer.</jc>
263    *    UonSerializer s2 = UonSerializer
264    *       .<jsm>create</jsm>()
265    *       .paramFormat(<jsf>PLAIN_TEXT</jsf>)
266    *       .build();
267    *
268    *    OMap m = OMap.<jsm>of</jsm>(
269    *       <js>"foo"</js>, <js>"bar"</js>,
270    *       <js>"baz"</js>, <jk>new</jk> String[]{<js>"qux"</js>, <js>"true"</js>, <js>"123"</js>}
271    *    );
272    *
273    *    <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc>
274    *    String uon1 = s1.serialize(m)
275    *
276    *    <jc>// Produces: "foo=bar,baz=qux,true,123"</jc>
277    *    String uon2 = s2.serialize(m)
278    * </p>
279    */
280   public static final String UON_paramFormat = PREFIX + ".paramFormat.s";
281
282
283   //-------------------------------------------------------------------------------------------------------------------
284   // Predefined instances
285   //-------------------------------------------------------------------------------------------------------------------
286
287   /** Reusable instance of {@link UonSerializer}, all default settings. */
288   public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.DEFAULT);
289
290   /** Reusable instance of {@link UonSerializer.Readable}. */
291   public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT);
292
293   /** Reusable instance of {@link UonSerializer.Encoding}. */
294   public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.DEFAULT);
295
296
297   //-------------------------------------------------------------------------------------------------------------------
298   // Predefined subclasses
299   //-------------------------------------------------------------------------------------------------------------------
300
301   /**
302    * Equivalent to <code>UonSerializer.<jsm>create</jsm>().ws().build();</code>.
303    */
304   public static class Readable extends UonSerializer {
305
306      /**
307       * Constructor.
308       *
309       * @param ps The property store containing all the settings for this object.
310       */
311      public Readable(PropertyStore ps) {
312         super(ps.builder().setDefault(WSERIALIZER_useWhitespace, true).build());
313      }
314   }
315
316   /**
317    * Equivalent to <code>UonSerializer.<jsm>create</jsm>().encoding().build();</code>.
318    */
319   public static class Encoding extends UonSerializer {
320
321      /**
322       * Constructor.
323       *
324       * @param ps The property store containing all the settings for this object.
325       */
326      public Encoding(PropertyStore ps) {
327         super(ps.builder().setDefault(UON_encoding, true).build());
328      }
329   }
330
331   /**
332    * Converts the specified value to a string that can be used as an HTTP header value, query parameter value,
333    * form-data parameter, or URI path variable.
334    *
335    * <p>
336    * Returned values should NOT be URL-encoded.
337    *
338    * @param partType The category of value being serialized.
339    * @param schema
340    *    Schema information about the part.
341    *    <br>May be <jk>null</jk>.
342    *    <br>Not all part serializers use the schema information.
343    * @param value The value being serialized.
344    * @return The serialized value.
345    * @throws SerializeException If a problem occurred while trying to parse the input.
346    * @throws SchemaValidationException If the output fails schema validation.
347    */
348   public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
349      return createPartSession(null).serialize(partType, schema, value);
350   }
351
352   //-------------------------------------------------------------------------------------------------------------------
353   // Instance
354   //-------------------------------------------------------------------------------------------------------------------
355
356   private final boolean
357      encoding,
358      addBeanTypes;
359
360   private final char
361      quoteChar;
362
363   private final ParamFormat
364      paramFormat;
365
366   private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>();
367   private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>();
368
369   /**
370    * Constructor.
371    *
372    * @param ps
373    *    The property store containing all the settings for this object.
374    */
375   public UonSerializer(PropertyStore ps) {
376      this(ps, "text/uon", (String)null);
377   }
378
379   /**
380    * No-arg constructor.
381    */
382   public UonSerializer() {
383      this(PropertyStore.DEFAULT, "text/uon", (String)null);
384   }
385
386   /**
387    * Constructor.
388    *
389    * @param ps
390    *    The property store containing all the settings for this object.
391    * @param produces
392    *    The media type that this serializer produces.
393    * @param accept
394    *    The accept media types that the serializer can handle.
395    *    <p>
396    *    Can contain meta-characters per the <c>media-type</c> specification of {@doc ExtRFC2616.section14.1}
397    *    <p>
398    *    If empty, then assumes the only media type supported is <c>produces</c>.
399    *    <p>
400    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
401    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
402    *    <p class='bcode w800'>
403    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
404    *    </p>
405    *    <br>...or...
406    *    <p class='bcode w800'>
407    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
408    *    </p>
409    * <p>
410    * The accept value can also contain q-values.
411    */
412   public UonSerializer(PropertyStore ps, String produces, String accept) {
413      super(ps, produces, accept);
414      encoding = getBooleanProperty(UON_encoding, false);
415      addBeanTypes = getBooleanProperty(UON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
416      paramFormat = getProperty(UON_paramFormat, ParamFormat.class, ParamFormat.UON);
417      quoteChar = getStringProperty(WSERIALIZER_quoteChar, "'").charAt(0);
418   }
419
420   @Override /* Context */
421   public UonSerializerBuilder builder() {
422      return new UonSerializerBuilder(getPropertyStore());
423   }
424
425   /**
426    * Instantiates a new clean-slate {@link UonSerializerBuilder} object.
427    *
428    * <p>
429    * This is equivalent to simply calling <code><jk>new</jk> UonSerializerBuilder()</code>.
430    *
431    * <p>
432    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
433    * the settings of the object called on.
434    *
435    * @return A new {@link UonSerializerBuilder} object.
436    */
437   public static UonSerializerBuilder create() {
438      return new UonSerializerBuilder();
439   }
440
441
442   //-----------------------------------------------------------------------------------------------------------------
443   // Entry point methods
444   //-----------------------------------------------------------------------------------------------------------------
445
446   @Override /* Context */
447   public  UonSerializerSession createSession() {
448      return createSession(createDefaultSessionArgs());
449   }
450
451   @Override /* Serializer */
452   public UonSerializerSession createSession(SerializerSessionArgs args) {
453      return new UonSerializerSession(this, null, args);
454   }
455
456   @Override /* HttpPartSerializer */
457   public UonSerializerSession createPartSession(SerializerSessionArgs args) {
458      return new UonSerializerSession(this, null, args);
459   }
460
461   //-----------------------------------------------------------------------------------------------------------------
462   // Extended metadata
463   //-----------------------------------------------------------------------------------------------------------------
464
465   @Override /* UonMetaProvider */
466   public UonClassMeta getUonClassMeta(ClassMeta<?> cm) {
467      UonClassMeta m = uonClassMetas.get(cm);
468      if (m == null) {
469         m = new UonClassMeta(cm, this);
470         uonClassMetas.put(cm, m);
471      }
472      return m;
473   }
474
475   @Override /* UonMetaProvider */
476   public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) {
477      if (bpm == null)
478         return UonBeanPropertyMeta.DEFAULT;
479      UonBeanPropertyMeta m = uonBeanPropertyMetas.get(bpm);
480      if (m == null) {
481         m = new UonBeanPropertyMeta(bpm.getDelegateFor(), this);
482         uonBeanPropertyMetas.put(bpm, m);
483      }
484      return m;
485   }
486
487   //-----------------------------------------------------------------------------------------------------------------
488   // Properties
489   //-----------------------------------------------------------------------------------------------------------------
490
491   /**
492    * Add <js>"_type"</js> properties when needed.
493    *
494    * @see #UON_addBeanTypes
495    * @return
496    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
497    *    through reflection.
498    */
499   @Override
500   protected final boolean isAddBeanTypes() {
501      return addBeanTypes;
502   }
503
504   /**
505    * Encode non-valid URI characters.
506    *
507    * @see #UON_encoding
508    * @return
509    *    <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs.
510    */
511   protected final boolean isEncoding() {
512      return encoding;
513   }
514
515   /**
516    * Format to use for query/form-data/header values.
517    *
518    * @see #UON_paramFormat
519    * @return
520    *    Specifies the format to use for URL GET parameter keys and values.
521    */
522   protected final ParamFormat getParamFormat() {
523      return paramFormat;
524   }
525
526   /**
527    * Quote character.
528    *
529    * @see WriterSerializer#WSERIALIZER_quoteChar
530    * @return
531    *    The character used for quoting attributes and values.
532    */
533   @Override
534   protected final char getQuoteChar() {
535      return quoteChar;
536   }
537
538   //-----------------------------------------------------------------------------------------------------------------
539   // Other methods
540   //-----------------------------------------------------------------------------------------------------------------
541
542   @Override /* Context */
543   public OMap toMap() {
544      return super.toMap()
545         .a("UonSerializer", new DefaultFilteringOMap()
546            .a("encoding", encoding)
547            .a("addBeanTypes", addBeanTypes)
548            .a("paramFormat", paramFormat)
549         );
550   }
551}