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