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