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