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