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