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.annotation.*;
017import org.apache.juneau.httppart.*;
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 <c>Accept</c> types:  <bc>text/uon</bc>
027 * <p>
028 * Produces <c>Content-Type</c> types:  <bc>text/uon</bc>
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 */
118@ConfigurableContext
119public class UonSerializer extends WriterSerializer implements HttpPartSerializer {
120
121   //-------------------------------------------------------------------------------------------------------------------
122   // Configurable properties
123   //-------------------------------------------------------------------------------------------------------------------
124
125   static final String PREFIX = "UonSerializer";
126
127   /**
128    * Configuration property:  Add <js>"_type"</js> properties when needed.
129    *
130    * <h5 class='section'>Property:</h5>
131    * <ul>
132    *    <li><b>Name:</b>  <js>"UonSerializer.addBeanTypes.b"</js>
133    *    <li><b>Data type:</b>  <c>Boolean</c>
134    *    <li><b>Default:</b>  <jk>false</jk>
135    *    <li><b>Session property:</b>  <jk>false</jk>
136    *    <li><b>Methods:</b>
137    *       <ul>
138    *          <li class='jm'>{@link UonSerializerBuilder#addBeanTypes(boolean)}
139    *       </ul>
140    * </ul>
141    *
142    * <h5 class='section'>Description:</h5>
143    * <p>
144    * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred
145    * through reflection.
146    *
147    * <p>
148    * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is
149    * provided to customize the behavior of specific serializers in a {@link SerializerGroup}.
150    */
151   public static final String UON_addBeanTypes = PREFIX + ".addBeanTypes.b";
152
153   /**
154    * Configuration property:  Encode non-valid URI characters.
155    *
156    * <h5 class='section'>Property:</h5>
157    * <ul>
158    *    <li><b>Name:</b>  <js>"UonSerializer.encoding.b"</js>
159    *    <li><b>Data type:</b>  <c>Boolean</c>
160    *    <li><b>Default:</b>  <jk>false</jk> for {@link UonSerializer}, <jk>true</jk> for {@link UrlEncodingSerializer}
161    *    <li><b>Session property:</b>  <jk>false</jk>
162    *    <li><b>Methods:</b>
163    *       <ul>
164    *          <li class='jm'>{@link UonSerializerBuilder#encoding(boolean)}
165    *          <li class='jm'>{@link UonSerializerBuilder#encoding()}
166    *       </ul>
167    * </ul>
168    *
169    * <h5 class='section'>Description:</h5>
170    * <p>
171    * Encode non-valid URI characters with <js>"%xx"</js> constructs.
172    *
173    * <p>
174    * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences.
175    * <br>Set to <jk>false</jk> if parameter value is being passed to some other code that will already perform
176    * URL-encoding of non-valid URI characters.
177    *
178    * <h5 class='section'>Example:</h5>
179    * <p class='bcode w800'>
180    *    <jc>// Create a non-encoding UON serializer.</jc>
181    *    UonSerializer s1 = UonSerializer.
182    *       .<jsm>create</jsm>()
183    *       .build();
184    *
185    *    <jc>// Create an encoding UON serializer.</jc>
186    *    UonSerializer s2 = UonSerializer.
187    *       .<jsm>create</jsm>()
188    *       .encoding()
189    *       .build();
190    *
191    *    ObjectMap m = <jk>new</jk> ObjectMap().append("foo", "foo bar");
192    *
193    *    <jc>// Produces: "(foo=foo bar)"</jc>
194    *    String uon1 = s1.serialize(m)
195    *
196    *    <jc>// Produces: "(foo=foo%20bar)"</jc>
197    *    String uon2 = s2.serialize(m)
198    * </p>
199    */
200   public static final String UON_encoding = PREFIX + ".encoding.b";
201
202   /**
203    * Configuration property:  Format to use for query/form-data/header values.
204    *
205    * <h5 class='section'>Property:</h5>
206    * <ul>
207    *    <li><b>Name:</b>  <js>"UonSerializer.paramFormat.s"</js>
208    *    <li><b>Data type:</b>  <c>String</c> ({@link ParamFormat})
209    *    <li><b>Default:</b>  <js>"UON"</js>
210    *    <li><b>Session property:</b>  <jk>false</jk>
211    *    <li><b>Methods:</b>
212    *       <ul>
213    *          <li class='jm'>{@link UonSerializerBuilder#paramFormat(ParamFormat)}
214    *          <li class='jm'>{@link UonSerializerBuilder#paramFormatPlain()}
215    *       </ul>
216    * </ul>
217    *
218    * <h5 class='section'>Description:</h5>
219    * <p>
220    * Specifies the format to use for URL GET parameter keys and values.
221    *
222    * <p>
223    * Possible values:
224    * <ul class='javatree'>
225    *    <li class='jf'>{@link ParamFormat#UON} - Use UON notation for parameters.
226    *    <li class='jf'>{@link ParamFormat#PLAINTEXT} - Use plain text for parameters.
227    * </ul>
228    *
229    * <h5 class='section'>Example:</h5>
230    * <p class='bcode w800'>
231    *    <jc>// Create a normal UON serializer.</jc>
232    *    UonSerializer s1 = UonSerializer.
233    *       .<jsm>create</jsm>()
234    *       .build();
235    *
236    *    <jc>// Create a plain-text UON serializer.</jc>
237    *    UonSerializer s2 = UonSerializer.
238    *       .<jsm>create</jsm>()
239    *       .paramFormat(<jsf>PLAIN_TEXT</jsf>)
240    *       .build();
241    *
242    *    ObjectMap m = <jk>new</jk> ObjectMap()
243    *       .append(<js>"foo"</js>, <js>"bar"</js>);
244    *       .append(<js>"baz"</js>, <jk>new</jk> String[]{<js>"qux"</js>, <js>"true"</js>, <js>"123"</js>});
245    *
246    *    <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc>
247    *    String uon1 = s1.serialize(m)
248    *
249    *    <jc>// Produces: "foo=bar,baz=qux,true,123"</jc>
250    *    String uon2 = s2.serialize(m)
251    * </p>
252    */
253   public static final String UON_paramFormat = PREFIX + ".paramFormat.s";
254
255
256   //-------------------------------------------------------------------------------------------------------------------
257   // Predefined instances
258   //-------------------------------------------------------------------------------------------------------------------
259
260   /** Reusable instance of {@link UonSerializer}, all default settings. */
261   public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.DEFAULT);
262
263   /** Reusable instance of {@link UonSerializer.Readable}. */
264   public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT);
265
266   /** Reusable instance of {@link UonSerializer.Encoding}. */
267   public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.DEFAULT);
268
269
270   //-------------------------------------------------------------------------------------------------------------------
271   // Predefined subclasses
272   //-------------------------------------------------------------------------------------------------------------------
273
274   /**
275    * Equivalent to <code>UonSerializer.<jsm>create</jsm>().ws().build();</code>.
276    */
277   public static class Readable extends UonSerializer {
278
279      /**
280       * Constructor.
281       *
282       * @param ps The property store containing all the settings for this object.
283       */
284      public Readable(PropertyStore ps) {
285         super(ps.builder().set(WSERIALIZER_useWhitespace, true).build());
286      }
287   }
288
289   /**
290    * Equivalent to <code>UonSerializer.<jsm>create</jsm>().encoding().build();</code>.
291    */
292   public static class Encoding extends UonSerializer {
293
294      /**
295       * Constructor.
296       *
297       * @param ps The property store containing all the settings for this object.
298       */
299      public Encoding(PropertyStore ps) {
300         super(ps.builder().set(UON_encoding, true).build());
301      }
302   }
303
304
305   //-------------------------------------------------------------------------------------------------------------------
306   // Instance
307   //-------------------------------------------------------------------------------------------------------------------
308
309   private final boolean
310      encoding,
311      addBeanTypes;
312
313   private final ParamFormat
314      paramFormat;
315
316   /**
317    * Constructor.
318    *
319    * @param ps
320    *    The property store containing all the settings for this object.
321    */
322   public UonSerializer(PropertyStore ps) {
323      this(ps, "text/uon", (String)null);
324   }
325
326   /**
327    * Constructor.
328    *
329    * @param ps
330    *    The property store containing all the settings for this object.
331    * @param produces
332    *    The media type that this serializer produces.
333    * @param accept
334    *    The accept media types that the serializer can handle.
335    *    <p>
336    *    Can contain meta-characters per the <c>media-type</c> specification of {@doc RFC2616.section14.1}
337    *    <p>
338    *    If empty, then assumes the only media type supported is <c>produces</c>.
339    *    <p>
340    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
341    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
342    *    <p class='bcode w800'>
343    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
344    *    </p>
345    *    <br>...or...
346    *    <p class='bcode w800'>
347    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
348    *    </p>
349    * <p>
350    * The accept value can also contain q-values.
351    */
352   public UonSerializer(PropertyStore ps, String produces, String accept) {
353      super(ps, produces, accept);
354      encoding = getBooleanProperty(UON_encoding, false);
355      addBeanTypes = getBooleanProperty(UON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false));
356      paramFormat = getProperty(UON_paramFormat, ParamFormat.class, ParamFormat.UON);
357   }
358
359   @Override /* Context */
360   public UonSerializerBuilder builder() {
361      return new UonSerializerBuilder(getPropertyStore());
362   }
363
364   /**
365    * Instantiates a new clean-slate {@link UonSerializerBuilder} object.
366    *
367    * <p>
368    * This is equivalent to simply calling <code><jk>new</jk> UonSerializerBuilder()</code>.
369    *
370    * <p>
371    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
372    * the settings of the object called on.
373    *
374    * @return A new {@link UonSerializerBuilder} object.
375    */
376   public static UonSerializerBuilder create() {
377      return new UonSerializerBuilder();
378   }
379
380
381   //-----------------------------------------------------------------------------------------------------------------
382   // Entry point methods
383   //-----------------------------------------------------------------------------------------------------------------
384
385   @Override /* Context */
386   public  UonSerializerSession createSession() {
387      return createSession(createDefaultSessionArgs());
388   }
389
390   @Override /* Serializer */
391   public UonSerializerSession createSession(SerializerSessionArgs args) {
392      return new UonSerializerSession(this, null, args);
393   }
394
395   @Override /* HttpPartSerializer */
396   public UonSerializerSession createPartSession(SerializerSessionArgs args) {
397      return new UonSerializerSession(this, null, args);
398   }
399
400   @Override /* HttpPartSerializer */
401   public UonSerializerSession createPartSession() {
402      return createPartSession(null);
403   }
404
405   @Override /* HttpPartSerializer */
406   public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
407      return createPartSession().serialize(partType, schema, value);
408   }
409
410   @Override /* HttpPartSerializer */
411   public String serialize(HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
412      return createPartSession().serialize(null, schema, value);
413   }
414
415   //-----------------------------------------------------------------------------------------------------------------
416   // Properties
417   //-----------------------------------------------------------------------------------------------------------------
418
419   /**
420    * Configuration property:  Add <js>"_type"</js> properties when needed.
421    *
422    * @see #UON_addBeanTypes
423    * @return
424    *    <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred
425    *    through reflection.
426    */
427   @Override
428   protected final boolean isAddBeanTypes() {
429      return addBeanTypes;
430   }
431
432   /**
433    * Configuration property:  Encode non-valid URI characters.
434    *
435    * @see #UON_encoding
436    * @return
437    *    <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs.
438    */
439   protected final boolean isEncoding() {
440      return encoding;
441   }
442
443   /**
444    * Configuration property:  Format to use for query/form-data/header values.
445    *
446    * @see #UON_paramFormat
447    * @return
448    *    Specifies the format to use for URL GET parameter keys and values.
449    */
450   protected final ParamFormat getParamFormat() {
451      return paramFormat;
452   }
453
454   //-----------------------------------------------------------------------------------------------------------------
455   // Other methods
456   //-----------------------------------------------------------------------------------------------------------------
457
458   @Override /* Context */
459   public ObjectMap toMap() {
460      return super.toMap()
461         .append("UonSerializer", new DefaultFilteringObjectMap()
462            .append("encoding", encoding)
463            .append("addBeanTypes", addBeanTypes)
464            .append("paramFormat", paramFormat)
465         );
466   }
467}