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.uon; 014 015import org.apache.juneau.*; 016import org.apache.juneau.httppart.*; 017import org.apache.juneau.serializer.*; 018import org.apache.juneau.urlencoding.*; 019 020/** 021 * Serializes POJO models to UON (a notation for URL-encoded query parameter values). 022 * 023 * <h5 class='topic'>Media types</h5> 024 * 025 * Handles <code>Accept</code> types: <code><b>text/uon</b></code> 026 * <p> 027 * Produces <code>Content-Type</code> types: <code><b>text/uon</b></code> 028 * 029 * <h5 class='topic'>Description</h5> 030 * 031 * This serializer provides several serialization options. 032 * Typically, one of the predefined DEFAULT serializers will be sufficient. 033 * 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 UON notation would be as follows: 062 * <p class='bcode w800'> 063 * ( 064 * <ua>id</ua>=<un>1</un>, 065 * <ua>name</ua>=<us>'John+Smith'</us>, 066 * <ua>uri</ua>=<us>http://sample/addressBook/person/1</us>, 067 * <ua>addressBookUri</ua>=<us>http://sample/addressBook</us>, 068 * <ua>birthDate</ua>=<us>1946-08-12T00:00:00Z</us>, 069 * <ua>otherIds</ua>=<uk>null</uk>, 070 * <ua>addresses</ua>=@( 071 * ( 072 * <ua>uri</ua>=<us>http://sample/addressBook/address/1</us>, 073 * <ua>personUri</ua>=<us>http://sample/addressBook/person/1</us>, 074 * <ua>id</ua>=<un>1</un>, 075 * <ua>street</ua>=<us>'100+Main+Street'</us>, 076 * <ua>city</ua>=<us>Anywhereville</us>, 077 * <ua>state</ua>=<us>NY</us>, 078 * <ua>zip</ua>=<un>12345</un>, 079 * <ua>isCurrent</ua>=<uk>true</uk> 080 * ) 081 * ) 082 * ) 083 * </p> 084 * 085 * <h5 class='section'>Example:</h5> 086 * <p class='bcode w800'> 087 * <jc>// Serialize a Map</jc> 088 * Map m = <jk>new</jk> ObjectMap(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>); 089 * 090 * <jc>// Serialize to value equivalent to JSON.</jc> 091 * <jc>// Produces "(a=b,c=1,d=false,e=@(f,1,false),g=(h=i))"</jc> 092 * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); 093 * 094 * <jc>// Serialize a bean</jc> 095 * <jk>public class</jk> Person { 096 * <jk>public</jk> Person(String s); 097 * <jk>public</jk> String getName(); 098 * <jk>public int</jk> getAge(); 099 * <jk>public</jk> Address getAddress(); 100 * <jk>public boolean</jk> deceased; 101 * } 102 * 103 * <jk>public class</jk> Address { 104 * <jk>public</jk> String getStreet(); 105 * <jk>public</jk> String getCity(); 106 * <jk>public</jk> String getState(); 107 * <jk>public int</jk> getZip(); 108 * } 109 * 110 * Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>, 111 * <js>"NY"</js>, 12345, <jk>false</jk>); 112 * 113 * <jc>// Produces "(name='John Doe',age=23,address=(street='123 Main St',city=Anywhere,state=NY,zip=12345),deceased=false)"</jc> 114 * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); 115 * </p> 116 */ 117public class UonSerializer extends WriterSerializer implements HttpPartSerializer { 118 119 //------------------------------------------------------------------------------------------------------------------- 120 // Configurable properties 121 //------------------------------------------------------------------------------------------------------------------- 122 123 private static final String PREFIX = "UonSerializer."; 124 125 /** 126 * Configuration property: Add <js>"_type"</js> properties when needed. 127 * 128 * <h5 class='section'>Property:</h5> 129 * <ul> 130 * <li><b>Name:</b> <js>"UonSerializer.addBeanTypes.b"</js> 131 * <li><b>Data type:</b> <code>Boolean</code> 132 * <li><b>Default:</b> <jk>false</jk> 133 * <li><b>Session property:</b> <jk>false</jk> 134 * <li><b>Methods:</b> 135 * <ul> 136 * <li class='jm'>{@link UonSerializerBuilder#addBeanTypes(boolean)} 137 * </ul> 138 * </ul> 139 * 140 * <h5 class='section'>Description:</h5> 141 * <p> 142 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 143 * through reflection. 144 * 145 * <p> 146 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 147 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 148 */ 149 public static final String UON_addBeanTypes = PREFIX + "addBeanTypes.b"; 150 151 /** 152 * Configuration property: Encode non-valid URI characters. 153 * 154 * <h5 class='section'>Property:</h5> 155 * <ul> 156 * <li><b>Name:</b> <js>"UonSerializer.encoding.b"</js> 157 * <li><b>Data type:</b> <code>Boolean</code> 158 * <li><b>Default:</b> <jk>false</jk> for {@link UonSerializer}, <jk>true</jk> for {@link UrlEncodingSerializer} 159 * <li><b>Session property:</b> <jk>false</jk> 160 * <li><b>Methods:</b> 161 * <ul> 162 * <li class='jm'>{@link UonSerializerBuilder#encoding(boolean)} 163 * <li class='jm'>{@link UonSerializerBuilder#encoding()} 164 * </ul> 165 * </ul> 166 * 167 * <h5 class='section'>Description:</h5> 168 * <p> 169 * Encode non-valid URI characters with <js>"%xx"</js> constructs. 170 * 171 * <p> 172 * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences. 173 * <br>Set to <jk>false</jk> if parameter value is being passed to some other code that will already perform 174 * URL-encoding of non-valid URI characters. 175 * 176 * <h5 class='section'>Example:</h5> 177 * <p class='bcode w800'> 178 * <jc>// Create a non-encoding UON serializer.</jc> 179 * UonSerializer s1 = UonSerializer. 180 * .<jsm>create</jsm>() 181 * .build(); 182 * 183 * <jc>// Create an encoding UON serializer.</jc> 184 * UonSerializer s2 = UonSerializer. 185 * .<jsm>create</jsm>() 186 * .encoding() 187 * .build(); 188 * 189 * ObjectMap m = <jk>new</jk> ObjectMap().append("foo", "foo bar"); 190 * 191 * <jc>// Produces: "(foo=foo bar)"</jc> 192 * String uon1 = s1.serialize(m) 193 * 194 * <jc>// Produces: "(foo=foo%20bar)"</jc> 195 * String uon2 = s2.serialize(m) 196 * </p> 197 */ 198 public static final String UON_encoding = PREFIX + "encoding.b"; 199 200 /** 201 * Configuration property: Format to use for query/form-data/header values. 202 * 203 * <h5 class='section'>Property:</h5> 204 * <ul> 205 * <li><b>Name:</b> <js>"UrlEncodingSerializer.paramFormat.s"</js> 206 * <li><b>Data type:</b> <code>String</code> ({@link ParamFormat}) 207 * <li><b>Default:</b> <js>"UON"</js> 208 * <li><b>Session property:</b> <jk>false</jk> 209 * <li><b>Methods:</b> 210 * <ul> 211 * <li class='jm'>{@link UonSerializerBuilder#paramFormat(ParamFormat)} 212 * <li class='jm'>{@link UonSerializerBuilder#paramFormatPlain()} 213 * </ul> 214 * </ul> 215 * 216 * <h5 class='section'>Description:</h5> 217 * <p> 218 * Specifies the format to use for URL GET parameter keys and values. 219 * 220 * <p> 221 * Possible values: 222 * <ul> 223 * <li class='jf'>{@link ParamFormat#UON} - Use UON notation for parameters. 224 * <li class='jf'>{@link ParamFormat#PLAINTEXT} - Use plain text for parameters. 225 * </ul> 226 * 227 * <h5 class='section'>Example:</h5> 228 * <p class='bcode w800'> 229 * <jc>// Create a normal UON serializer.</jc> 230 * UonSerializer s1 = UonSerializer. 231 * .<jsm>create</jsm>() 232 * .build(); 233 * 234 * <jc>// Create a plain-text UON serializer.</jc> 235 * UonSerializer s2 = UonSerializer. 236 * .<jsm>create</jsm>() 237 * .paramFormat(<jsf>PLAIN_TEXT</jsf>) 238 * .build(); 239 * 240 * ObjectMap m = <jk>new</jk> ObjectMap() 241 * .append(<js>"foo"</js>, <js>"bar"</js>); 242 * .append(<js>"baz"</js>, <jk>new</jk> String[]{<js>"qux"</js>, <js>"true"</js>, <js>"123"</js>}); 243 * 244 * <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc> 245 * String uon1 = s1.serialize(m) 246 * 247 * <jc>// Produces: "foo=bar,baz=qux,true,123"</jc> 248 * String uon2 = s2.serialize(m) 249 * </p> 250 */ 251 public static final String UON_paramFormat = PREFIX + "paramFormat.s"; 252 253 254 //------------------------------------------------------------------------------------------------------------------- 255 // Predefined instances 256 //------------------------------------------------------------------------------------------------------------------- 257 258 /** Reusable instance of {@link UonSerializer}, all default settings. */ 259 public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.DEFAULT); 260 261 /** Reusable instance of {@link UonSerializer.Readable}. */ 262 public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 263 264 /** Reusable instance of {@link UonSerializer.Encoding}. */ 265 public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.DEFAULT); 266 267 268 //------------------------------------------------------------------------------------------------------------------- 269 // Predefined subclasses 270 //------------------------------------------------------------------------------------------------------------------- 271 272 /** 273 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().ws().build();</code>. 274 */ 275 public static class Readable extends UonSerializer { 276 277 /** 278 * Constructor. 279 * 280 * @param ps The property store containing all the settings for this object. 281 */ 282 public Readable(PropertyStore ps) { 283 super(ps.builder().set(SERIALIZER_useWhitespace, true).build()); 284 } 285 } 286 287 /** 288 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().encoding().build();</code>. 289 */ 290 public static class Encoding extends UonSerializer { 291 292 /** 293 * Constructor. 294 * 295 * @param ps The property store containing all the settings for this object. 296 */ 297 public Encoding(PropertyStore ps) { 298 super(ps.builder().set(UON_encoding, true).build()); 299 } 300 } 301 302 303 //------------------------------------------------------------------------------------------------------------------- 304 // Instance 305 //------------------------------------------------------------------------------------------------------------------- 306 307 private final boolean 308 encodeChars, 309 addBeanTypes; 310 311 private final ParamFormat 312 paramFormat; 313 314 /** 315 * Constructor. 316 * 317 * @param ps 318 * The property store containing all the settings for this object. 319 */ 320 public UonSerializer(PropertyStore ps) { 321 this(ps, "text/uon", null); 322 } 323 324 /** 325 * Constructor. 326 * 327 * @param ps 328 * The property store containing all the settings for this object. 329 * @param produces 330 * The media type that this serializer produces. 331 * @param accept 332 * The accept media types that the serializer can handle. 333 * <p> 334 * Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1} 335 * <p> 336 * If empty, then assumes the only media type supported is <code>produces</code>. 337 * <p> 338 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 339 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 340 * <p class='bcode w800'> 341 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 342 * </p> 343 * <br>...or... 344 * <p class='bcode w800'> 345 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 346 * </p> 347 * <p> 348 * The accept value can also contain q-values. 349 */ 350 public UonSerializer(PropertyStore ps, String produces, String accept) { 351 super(ps, produces, accept); 352 encodeChars = getBooleanProperty(UON_encoding, false); 353 addBeanTypes = getBooleanProperty(UON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false)); 354 paramFormat = getProperty(UON_paramFormat, ParamFormat.class, ParamFormat.UON); 355 } 356 357 @Override /* Context */ 358 public UonSerializerBuilder builder() { 359 return new UonSerializerBuilder(getPropertyStore()); 360 } 361 362 /** 363 * Instantiates a new clean-slate {@link UonSerializerBuilder} object. 364 * 365 * <p> 366 * This is equivalent to simply calling <code><jk>new</jk> UonSerializerBuilder()</code>. 367 * 368 * <p> 369 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 370 * the settings of the object called on. 371 * 372 * @return A new {@link UonSerializerBuilder} object. 373 */ 374 public static UonSerializerBuilder create() { 375 return new UonSerializerBuilder(); 376 } 377 378 379 //----------------------------------------------------------------------------------------------------------------- 380 // Entry point methods 381 //----------------------------------------------------------------------------------------------------------------- 382 383 @Override /* Serializer */ 384 public WriterSerializerSession createSession(SerializerSessionArgs args) { 385 return new UonSerializerSession(this, null, args); 386 } 387 388 @Override /* HttpPartSerializer */ 389 public UonSerializerSession createPartSession(SerializerSessionArgs args) { 390 return new UonSerializerSession(this, null, args); 391 } 392 393 @Override /* HttpPartSerializer */ 394 public UonSerializerSession createPartSession() { 395 return createPartSession(null); 396 } 397 398 @Override /* HttpPartSerializer */ 399 public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException { 400 return createPartSession().serialize(partType, schema, value); 401 } 402 403 @Override /* HttpPartSerializer */ 404 public String serialize(HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException { 405 return createPartSession().serialize(null, schema, value); 406 } 407 408 //----------------------------------------------------------------------------------------------------------------- 409 // Properties 410 //----------------------------------------------------------------------------------------------------------------- 411 412 /** 413 * Configuration property: Encode non-valid URI characters. 414 * 415 * @see #UON_encoding 416 * @return 417 * <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs. 418 */ 419 protected final boolean isEncodeChars() { 420 return encodeChars; 421 } 422 423 /** 424 * Configuration property: Add <js>"_type"</js> properties when needed. 425 * 426 * @see #UON_addBeanTypes 427 * @return 428 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 429 * through reflection. 430 */ 431 @Override 432 protected final boolean isAddBeanTypes() { 433 return addBeanTypes; 434 } 435 436 /** 437 * Configuration property: Format to use for query/form-data/header values. 438 * 439 * @see #UON_paramFormat 440 * @return 441 * Specifies the format to use for URL GET parameter keys and values. 442 */ 443 protected final ParamFormat getParamFormat() { 444 return paramFormat; 445 } 446 447 @Override /* Context */ 448 public ObjectMap asMap() { 449 return super.asMap() 450 .append("UonSerializer", new ObjectMap() 451 .append("encodeChars", encodeChars) 452 .append("addBeanTypes", addBeanTypes) 453 .append("paramFormat", paramFormat) 454 ); 455 } 456}