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