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.urlencoding;
014
015import org.apache.juneau.*;
016import org.apache.juneau.serializer.*;
017import org.apache.juneau.uon.*;
018
019/**
020 * Serializes POJO models to URL-encoded notation with UON-encoded values (a notation for URL-encoded query paramter values).
021 *
022 * <h5 class='section'>Media types:</h5>
023 *
024 * Handles <code>Accept</code> types:  <code><b>application/x-www-form-urlencoded</b></code>
025 * <p>
026 * Produces <code>Content-Type</code> types:  <code><b>application/x-www-form-urlencoded</b></code>
027 *
028 * <h5 class='topic'>Description</h5>
029 *
030 * This serializer provides several serialization options.
031 * <br>Typically, one of the predefined DEFAULT serializers will be sufficient.
032 * <br>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 w800'>
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 URL-encoded notation would be as follows:
061 * <p class='bcode w800'>
062 *    <ua>id</ua>=<un>1</un>
063 *    &amp;<ua>name</ua>=<us>'John+Smith'</us>,
064 *    &amp;<ua>uri</ua>=<us>http://sample/addressBook/person/1</us>,
065 *    &amp;<ua>addressBookUri</ua>=<us>http://sample/addressBook</us>,
066 *    &amp;<ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>,
067 *    &amp;<ua>otherIds</ua>=<uk>null</uk>,
068 *    &amp;<ua>addresses</ua>=@(
069 *       (
070 *          <ua>uri</ua>=<us>http://sample/addressBook/address/1</us>,
071 *          <ua>personUri</ua>=<us>http://sample/addressBook/person/1</us>,
072 *          <ua>id</ua>=<un>1</un>,
073 *          <ua>street</ua>=<us>'100+Main+Street'</us>,
074 *          <ua>city</ua>=<us>Anywhereville</us>,
075 *          <ua>state</ua>=<us>NY</us>,
076 *          <ua>zip</ua>=<un>12345</un>,
077 *          <ua>isCurrent</ua>=<uk>true</uk>
078 *       )
079 *    )
080 * </p>
081 *
082 * <h5 class='section'>Example:</h5>
083 * <p class='bcode w800'>
084 *    <jc>// Serialize a Map</jc>
085 *    Map m = <jk>new</jk> ObjectMap(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
086 *
087 *    <jc>// Serialize to value equivalent to JSON.</jc>
088 *    <jc>// Produces "a=b&amp;c=1&amp;d=false&amp;e=@(f,1,false)&amp;g=(h=i)"</jc>
089 *    String s = UrlEncodingSerializer.<jsf>DEFAULT</jsf>.serialize(s);
090 *
091 *    <jc>// Serialize a bean</jc>
092 *    <jk>public class</jk> Person {
093 *       <jk>public</jk> Person(String s);
094 *       <jk>public</jk> String getName();
095 *       <jk>public int</jk> getAge();
096 *       <jk>public</jk> Address getAddress();
097 *       <jk>public boolean</jk> deceased;
098 *    }
099 *
100 *    <jk>public class</jk> Address {
101 *       <jk>public</jk> String getStreet();
102 *       <jk>public</jk> String getCity();
103 *       <jk>public</jk> String getState();
104 *       <jk>public int</jk> getZip();
105 *    }
106 *
107 *    Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>, <js>"NY"</js>, 12345, <jk>false</jk>);
108 *
109 *    <jc>// Produces "name=John+Doe&amp;age=23&amp;address=(street='123+Main+St',city=Anywhere,state=NY,zip=12345)&amp;deceased=false"</jc>
110 *    String s = UrlEncodingSerializer.<jsf>DEFAULT</jsf>.serialize(s);
111 * </p>
112 */
113public class UrlEncodingSerializer extends UonSerializer {
114
115   //-------------------------------------------------------------------------------------------------------------------
116   // Configurable properties
117   //-------------------------------------------------------------------------------------------------------------------
118
119   private static final String PREFIX = "UrlEncodingSerializer.";
120
121   /**
122    * Configuration property:  Serialize bean property collections/arrays as separate key/value pairs.
123    *
124    * <h5 class='section'>Property:</h5>
125    * <ul>
126    *    <li><b>Name:</b>  <js>"UrlEncodingSerializer.expandedParams.b"</js>
127    *    <li><b>Data type:</b>  <code>Boolean</code>
128    *    <li><b>Default:</b>  <jk>false</jk>
129    *    <li><b>Session property:</b>  <jk>false</jk>
130    *    <li><b>Methods:</b>
131    *       <ul>
132    *          <li class='jm'>{@link UrlEncodingSerializerBuilder#expandedParams(boolean)}
133    *          <li class='jm'>{@link UrlEncodingSerializerBuilder#expandedParams()}
134    *       </ul>
135    * </ul>
136    *
137    * <h5 class='section'>Description:</h5>
138    * <p>
139    * If <jk>false</jk>, serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>.
140    * <br>If <jk>true</jk>, serializing the same array results in <code>?key=1&amp;key=2&amp;key=3</code>.
141    *
142    * <p>
143    * This option only applies to beans.
144    *
145    * <h5 class='section'>Notes:</h5>
146    * <ul class='spaced-list'>
147    *    <li>
148    *       If parsing multi-part parameters, it's highly recommended to use <code>Collections</code> or <code>Lists</code>
149    *       as bean property types instead of arrays since arrays have to be recreated from scratch every time a value
150    *       is added to it.
151    * </ul>
152    *
153    * <h5 class='section'>Example:</h5>
154    * <p class='bcode w800'>
155    *    <jc>// A sample bean.</jc>
156    *    <jk>public class</jk> A {
157    *       <jk>public</jk> String[] f1 = {<js>"a"</js>,<js>"b"</js>};
158    *       <jk>public</jk> List&lt;String&gt; f2 = Arrays.<jsm>asList</jsm>(<jk>new</jk> String[]{<js>"c"</js>,<js>"d"</js>});
159    *    }
160    *
161    *    <jc>// Normal serializer.</jc>
162    *    WriterSerializer s1 = UrlEncodingSerializer.<jsf>DEFAULT</jsf>;
163    *
164    *    <jc>// Expanded-params serializer.</jc>
165    *    WriterSerializer s2 = UrlEncodingSerializer.<jsm>create</jsm>().expandedParams().build();
166    *
167    * <jc>// Produces "f1=(a,b)&amp;f2=(c,d)"</jc>
168    *    String ss1 = s1.serialize(<jk>new</jk> A());
169    *
170    *    <jc>// Produces "f1=a&amp;f1=b&amp;f2=c&amp;f2=d"</jc>
171    *    String ss2 = s2.serialize(<jk>new</jk> A()); <jc>
172    * </p>
173    *
174    */
175   public static final String URLENC_expandedParams = PREFIX + "expandedParams.b";
176
177
178   //-------------------------------------------------------------------------------------------------------------------
179   // Predefined instances
180   //-------------------------------------------------------------------------------------------------------------------
181
182   /** Reusable instance of {@link UrlEncodingSerializer}, all default settings. */
183   public static final UrlEncodingSerializer DEFAULT = new UrlEncodingSerializer(PropertyStore.DEFAULT);
184
185   /** Reusable instance of {@link UrlEncodingSerializer.PlainText}. */
186   public static final UrlEncodingSerializer DEFAULT_PLAINTEXT = new PlainText(PropertyStore.DEFAULT);
187
188   /** Reusable instance of {@link UrlEncodingSerializer.Expanded}. */
189   public static final UrlEncodingSerializer DEFAULT_EXPANDED = new Expanded(PropertyStore.DEFAULT);
190
191   /** Reusable instance of {@link UrlEncodingSerializer.Readable}. */
192   public static final UrlEncodingSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT);
193
194
195   //-------------------------------------------------------------------------------------------------------------------
196   // Predefined subclasses
197   //-------------------------------------------------------------------------------------------------------------------
198
199   /**
200    * Equivalent to <code>UrlEncodingSerializer.<jsm>create</jsm>().expandedParams().build();</code>.
201    */
202   public static class Expanded extends UrlEncodingSerializer {
203
204      /**
205       * Constructor.
206       *
207       * @param ps The property store containing all the settings for this object.
208       */
209      public Expanded(PropertyStore ps) {
210         super(ps.builder().set(URLENC_expandedParams, true).build());
211      }
212   }
213
214   /**
215    * Equivalent to <code>UrlEncodingSerializer.<jsm>create</jsm>().useWhitespace().build();</code>.
216    */
217   public static class Readable extends UrlEncodingSerializer {
218
219      /**
220       * Constructor.
221       *
222       * @param ps The property store containing all the settings for this object.
223       */
224      public Readable(PropertyStore ps) {
225         super(ps.builder().set(SERIALIZER_useWhitespace, true).build());
226      }
227   }
228
229   /**
230    * Equivalent to <code>UrlEncodingSerializer.<jsm>create</jsm>().plainTextParts().build();</code>.
231    */
232   public static class PlainText extends UrlEncodingSerializer {
233
234      /**
235       * Constructor.
236       *
237       * @param ps The property store containing all the settings for this object.
238       */
239      public PlainText(PropertyStore ps) {
240         super(ps.builder().set(UON_paramFormat, "PLAINTEXT").build());
241      }
242   }
243
244
245   //-------------------------------------------------------------------------------------------------------------------
246   // Instance
247   //-------------------------------------------------------------------------------------------------------------------
248
249   private final boolean
250      expandedParams;
251
252   /**
253    * Constructor.
254    *
255    * @param ps
256    *    The property store containing all the settings for this object.
257    */
258   public UrlEncodingSerializer(PropertyStore ps) {
259      this(ps, "application/x-www-form-urlencoded", (String)null);
260   }
261
262   /**
263    * Constructor.
264    *
265    * @param ps
266    *    The property store containing all the settings for this object.
267    * @param produces
268    *    The media type that this serializer produces.
269    * @param accept
270    *    The accept media types that the serializer can handle.
271    *    <p>
272    *    Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1}
273    *    <p>
274    *    If empty, then assumes the only media type supported is <code>produces</code>.
275    *    <p>
276    *    For example, if this serializer produces <js>"application/json"</js> but should handle media types of
277    *    <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be:
278    *    <p class='bcode w800'>
279    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>);
280    *    </p>
281    *    <br>...or...
282    *    <p class='bcode w800'>
283    *    <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*&#8203;/json"</js>);
284    *    </p>
285    * <p>
286    * The accept value can also contain q-values.
287    */
288   public UrlEncodingSerializer(PropertyStore ps, String produces, String accept) {
289      super(
290         ps.builder()
291            .set(UON_encoding, true)
292            .build(),
293         produces,
294         accept
295      );
296      expandedParams = getBooleanProperty(URLENC_expandedParams, false);
297   }
298
299   @Override /* Context */
300   public UrlEncodingSerializerBuilder builder() {
301      return new UrlEncodingSerializerBuilder(getPropertyStore());
302   }
303
304   /**
305    * Instantiates a new clean-slate {@link UrlEncodingSerializerBuilder} object.
306    *
307    * <p>
308    * This is equivalent to simply calling <code><jk>new</jk> UrlEncodingSerializerBuilder()</code>.
309    *
310    * <p>
311    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
312    * the settings of the object called on.
313    *
314    * @return A new {@link UrlEncodingSerializerBuilder} object.
315    */
316   public static UrlEncodingSerializerBuilder create() {
317      return new UrlEncodingSerializerBuilder();
318   }
319
320
321   //-----------------------------------------------------------------------------------------------------------------
322   // Entry point methods
323   //-----------------------------------------------------------------------------------------------------------------
324
325   @Override /* Serializer */
326   public WriterSerializerSession createSession(SerializerSessionArgs args) {
327      return new UrlEncodingSerializerSession(this, null, args);
328   }
329
330   //-----------------------------------------------------------------------------------------------------------------
331   // Properties
332   //-----------------------------------------------------------------------------------------------------------------
333
334   /**
335    * Configuration property:  Serialize bean property collections/arrays as separate key/value pairs.
336    *
337    * @see #URLENC_expandedParams
338    * @return
339    *    <jk>false</jk> if serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>.
340    *    <br><jk>true</jk> if serializing the same array results in <code>?key=1&amp;key=2&amp;key=3</code>.
341    */
342   protected final boolean isExpandedParams() {
343      return expandedParams;
344   }
345
346   @Override /* Context */
347   public ObjectMap asMap() {
348      return super.asMap()
349         .append("UrlEncodingSerializer", new ObjectMap()
350            .append("expandedParams", expandedParams)
351         );
352   }
353}