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.internal.*; 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 <code>Accept</code> types: <code><b>application/x-www-form-urlencoded</b></code> 026 * <p> 027 * Produces <code>Content-Type</code> types: <code><b>application/x-www-form-urlencoded</b></code> 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 * &<ua>name</ua>=<us>'John+Smith'</us>, 065 * &<ua>uri</ua>=<us>http://sample/addressBook/person/1</us>, 066 * &<ua>addressBookUri</ua>=<us>http://sample/addressBook</us>, 067 * &<ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>, 068 * &<ua>otherIds</ua>=<uk>null</uk>, 069 * &<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&c=1&d=false&e=@(f,1,false)&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&age=23&address=(street='123+Main+St',city=Anywhere,state=NY,zip=12345)&deceased=false"</jc> 111 * String s = UrlEncodingSerializer.<jsf>DEFAULT</jsf>.serialize(s); 112 * </p> 113 */ 114public class UrlEncodingSerializer extends UonSerializer { 115 116 //------------------------------------------------------------------------------------------------------------------- 117 // Configurable properties 118 //------------------------------------------------------------------------------------------------------------------- 119 120 private static final String PREFIX = "UrlEncodingSerializer."; 121 122 /** 123 * Configuration property: Serialize bean property collections/arrays as separate key/value pairs. 124 * 125 * <h5 class='section'>Property:</h5> 126 * <ul> 127 * <li><b>Name:</b> <js>"UrlEncodingSerializer.expandedParams.b"</js> 128 * <li><b>Data type:</b> <code>Boolean</code> 129 * <li><b>Default:</b> <jk>false</jk> 130 * <li><b>Session property:</b> <jk>false</jk> 131 * <li><b>Methods:</b> 132 * <ul> 133 * <li class='jm'>{@link UrlEncodingSerializerBuilder#expandedParams(boolean)} 134 * <li class='jm'>{@link UrlEncodingSerializerBuilder#expandedParams()} 135 * </ul> 136 * </ul> 137 * 138 * <h5 class='section'>Description:</h5> 139 * <p> 140 * If <jk>false</jk>, serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>. 141 * <br>If <jk>true</jk>, serializing the same array results in <code>?key=1&key=2&key=3</code>. 142 * 143 * <p> 144 * This option only applies to beans. 145 * 146 * <h5 class='section'>Notes:</h5> 147 * <ul class='spaced-list'> 148 * <li> 149 * If parsing multi-part parameters, it's highly recommended to use <code>Collections</code> or <code>Lists</code> 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<String> 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)&f2=(c,d)"</jc> 169 * String ss1 = s1.serialize(<jk>new</jk> A()); 170 * 171 * <jc>// Produces "f1=a&f1=b&f2=c&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(SERIALIZER_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 <code>media-type</code> specification of {@doc RFC2616.section14.1} 274 * <p> 275 * If empty, then assumes the only media type supported is <code>produces</code>. 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>"*​/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 /* Serializer */ 327 public WriterSerializerSession createSession(SerializerSessionArgs args) { 328 return new UrlEncodingSerializerSession(this, null, args); 329 } 330 331 //----------------------------------------------------------------------------------------------------------------- 332 // Properties 333 //----------------------------------------------------------------------------------------------------------------- 334 335 /** 336 * Configuration property: Serialize bean property collections/arrays as separate key/value pairs. 337 * 338 * @see #URLENC_expandedParams 339 * @return 340 * <jk>false</jk> if serializing the array <code>[1,2,3]</code> results in <code>?key=$a(1,2,3)</code>. 341 * <br><jk>true</jk> if serializing the same array results in <code>?key=1&key=2&key=3</code>. 342 */ 343 protected final boolean isExpandedParams() { 344 return expandedParams; 345 } 346 347 @Override /* Context */ 348 public ObjectMap asMap() { 349 return super.asMap() 350 .append("UrlEncodingSerializer", new ObjectMap() 351 .append("expandedParams", expandedParams) 352 ); 353 } 354 355 /** 356 * @deprecated Use {@link #UrlEncodingSerializer(PropertyStore, String, String)} 357 */ 358 @SuppressWarnings("javadoc") 359 @Deprecated 360 public UrlEncodingSerializer(PropertyStore ps, String produces, String...accept) { 361 this(ps, produces, StringUtils.join(accept, ",")); 362 } 363}