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