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 * &<ua>name</ua>=<us>'John+Smith'</us>, 064 * &<ua>uri</ua>=<us>http://sample/addressBook/person/1</us>, 065 * &<ua>addressBookUri</ua>=<us>http://sample/addressBook</us>, 066 * &<ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>, 067 * &<ua>otherIds</ua>=<uk>null</uk>, 068 * &<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&c=1&d=false&e=@(f,1,false)&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&age=23&address=(street='123+Main+St',city=Anywhere,state=NY,zip=12345)&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&key=2&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<String> 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)&f2=(c,d)"</jc> 168 * String ss1 = s1.serialize(<jk>new</jk> A()); 169 * 170 * <jc>// Produces "f1=a&f1=b&f2=c&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", 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>"*​/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&key=2&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}