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.annotation.*; 017import org.apache.juneau.httppart.*; 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 <c>Accept</c> types: <bc>text/uon</bc> 027 * <p> 028 * Produces <c>Content-Type</c> types: <bc>text/uon</bc> 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 */ 118@ConfigurableContext 119public class UonSerializer extends WriterSerializer implements HttpPartSerializer { 120 121 //------------------------------------------------------------------------------------------------------------------- 122 // Configurable properties 123 //------------------------------------------------------------------------------------------------------------------- 124 125 static final String PREFIX = "UonSerializer"; 126 127 /** 128 * Configuration property: Add <js>"_type"</js> properties when needed. 129 * 130 * <h5 class='section'>Property:</h5> 131 * <ul> 132 * <li><b>Name:</b> <js>"UonSerializer.addBeanTypes.b"</js> 133 * <li><b>Data type:</b> <c>Boolean</c> 134 * <li><b>Default:</b> <jk>false</jk> 135 * <li><b>Session property:</b> <jk>false</jk> 136 * <li><b>Methods:</b> 137 * <ul> 138 * <li class='jm'>{@link UonSerializerBuilder#addBeanTypes(boolean)} 139 * </ul> 140 * </ul> 141 * 142 * <h5 class='section'>Description:</h5> 143 * <p> 144 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 145 * through reflection. 146 * 147 * <p> 148 * When present, this value overrides the {@link #SERIALIZER_addBeanTypes} setting and is 149 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 150 */ 151 public static final String UON_addBeanTypes = PREFIX + ".addBeanTypes.b"; 152 153 /** 154 * Configuration property: Encode non-valid URI characters. 155 * 156 * <h5 class='section'>Property:</h5> 157 * <ul> 158 * <li><b>Name:</b> <js>"UonSerializer.encoding.b"</js> 159 * <li><b>Data type:</b> <c>Boolean</c> 160 * <li><b>Default:</b> <jk>false</jk> for {@link UonSerializer}, <jk>true</jk> for {@link UrlEncodingSerializer} 161 * <li><b>Session property:</b> <jk>false</jk> 162 * <li><b>Methods:</b> 163 * <ul> 164 * <li class='jm'>{@link UonSerializerBuilder#encoding(boolean)} 165 * <li class='jm'>{@link UonSerializerBuilder#encoding()} 166 * </ul> 167 * </ul> 168 * 169 * <h5 class='section'>Description:</h5> 170 * <p> 171 * Encode non-valid URI characters with <js>"%xx"</js> constructs. 172 * 173 * <p> 174 * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences. 175 * <br>Set to <jk>false</jk> if parameter value is being passed to some other code that will already perform 176 * URL-encoding of non-valid URI characters. 177 * 178 * <h5 class='section'>Example:</h5> 179 * <p class='bcode w800'> 180 * <jc>// Create a non-encoding UON serializer.</jc> 181 * UonSerializer s1 = UonSerializer. 182 * .<jsm>create</jsm>() 183 * .build(); 184 * 185 * <jc>// Create an encoding UON serializer.</jc> 186 * UonSerializer s2 = UonSerializer. 187 * .<jsm>create</jsm>() 188 * .encoding() 189 * .build(); 190 * 191 * ObjectMap m = <jk>new</jk> ObjectMap().append("foo", "foo bar"); 192 * 193 * <jc>// Produces: "(foo=foo bar)"</jc> 194 * String uon1 = s1.serialize(m) 195 * 196 * <jc>// Produces: "(foo=foo%20bar)"</jc> 197 * String uon2 = s2.serialize(m) 198 * </p> 199 */ 200 public static final String UON_encoding = PREFIX + ".encoding.b"; 201 202 /** 203 * Configuration property: Format to use for query/form-data/header values. 204 * 205 * <h5 class='section'>Property:</h5> 206 * <ul> 207 * <li><b>Name:</b> <js>"UonSerializer.paramFormat.s"</js> 208 * <li><b>Data type:</b> <c>String</c> ({@link ParamFormat}) 209 * <li><b>Default:</b> <js>"UON"</js> 210 * <li><b>Session property:</b> <jk>false</jk> 211 * <li><b>Methods:</b> 212 * <ul> 213 * <li class='jm'>{@link UonSerializerBuilder#paramFormat(ParamFormat)} 214 * <li class='jm'>{@link UonSerializerBuilder#paramFormatPlain()} 215 * </ul> 216 * </ul> 217 * 218 * <h5 class='section'>Description:</h5> 219 * <p> 220 * Specifies the format to use for URL GET parameter keys and values. 221 * 222 * <p> 223 * Possible values: 224 * <ul class='javatree'> 225 * <li class='jf'>{@link ParamFormat#UON} - Use UON notation for parameters. 226 * <li class='jf'>{@link ParamFormat#PLAINTEXT} - Use plain text for parameters. 227 * </ul> 228 * 229 * <h5 class='section'>Example:</h5> 230 * <p class='bcode w800'> 231 * <jc>// Create a normal UON serializer.</jc> 232 * UonSerializer s1 = UonSerializer. 233 * .<jsm>create</jsm>() 234 * .build(); 235 * 236 * <jc>// Create a plain-text UON serializer.</jc> 237 * UonSerializer s2 = UonSerializer. 238 * .<jsm>create</jsm>() 239 * .paramFormat(<jsf>PLAIN_TEXT</jsf>) 240 * .build(); 241 * 242 * ObjectMap m = <jk>new</jk> ObjectMap() 243 * .append(<js>"foo"</js>, <js>"bar"</js>); 244 * .append(<js>"baz"</js>, <jk>new</jk> String[]{<js>"qux"</js>, <js>"true"</js>, <js>"123"</js>}); 245 * 246 * <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc> 247 * String uon1 = s1.serialize(m) 248 * 249 * <jc>// Produces: "foo=bar,baz=qux,true,123"</jc> 250 * String uon2 = s2.serialize(m) 251 * </p> 252 */ 253 public static final String UON_paramFormat = PREFIX + ".paramFormat.s"; 254 255 256 //------------------------------------------------------------------------------------------------------------------- 257 // Predefined instances 258 //------------------------------------------------------------------------------------------------------------------- 259 260 /** Reusable instance of {@link UonSerializer}, all default settings. */ 261 public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.DEFAULT); 262 263 /** Reusable instance of {@link UonSerializer.Readable}. */ 264 public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 265 266 /** Reusable instance of {@link UonSerializer.Encoding}. */ 267 public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.DEFAULT); 268 269 270 //------------------------------------------------------------------------------------------------------------------- 271 // Predefined subclasses 272 //------------------------------------------------------------------------------------------------------------------- 273 274 /** 275 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().ws().build();</code>. 276 */ 277 public static class Readable extends UonSerializer { 278 279 /** 280 * Constructor. 281 * 282 * @param ps The property store containing all the settings for this object. 283 */ 284 public Readable(PropertyStore ps) { 285 super(ps.builder().set(WSERIALIZER_useWhitespace, true).build()); 286 } 287 } 288 289 /** 290 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().encoding().build();</code>. 291 */ 292 public static class Encoding extends UonSerializer { 293 294 /** 295 * Constructor. 296 * 297 * @param ps The property store containing all the settings for this object. 298 */ 299 public Encoding(PropertyStore ps) { 300 super(ps.builder().set(UON_encoding, true).build()); 301 } 302 } 303 304 305 //------------------------------------------------------------------------------------------------------------------- 306 // Instance 307 //------------------------------------------------------------------------------------------------------------------- 308 309 private final boolean 310 encoding, 311 addBeanTypes; 312 313 private final ParamFormat 314 paramFormat; 315 316 /** 317 * Constructor. 318 * 319 * @param ps 320 * The property store containing all the settings for this object. 321 */ 322 public UonSerializer(PropertyStore ps) { 323 this(ps, "text/uon", (String)null); 324 } 325 326 /** 327 * Constructor. 328 * 329 * @param ps 330 * The property store containing all the settings for this object. 331 * @param produces 332 * The media type that this serializer produces. 333 * @param accept 334 * The accept media types that the serializer can handle. 335 * <p> 336 * Can contain meta-characters per the <c>media-type</c> specification of {@doc RFC2616.section14.1} 337 * <p> 338 * If empty, then assumes the only media type supported is <c>produces</c>. 339 * <p> 340 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 341 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 342 * <p class='bcode w800'> 343 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 344 * </p> 345 * <br>...or... 346 * <p class='bcode w800'> 347 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 348 * </p> 349 * <p> 350 * The accept value can also contain q-values. 351 */ 352 public UonSerializer(PropertyStore ps, String produces, String accept) { 353 super(ps, produces, accept); 354 encoding = getBooleanProperty(UON_encoding, false); 355 addBeanTypes = getBooleanProperty(UON_addBeanTypes, getBooleanProperty(SERIALIZER_addBeanTypes, false)); 356 paramFormat = getProperty(UON_paramFormat, ParamFormat.class, ParamFormat.UON); 357 } 358 359 @Override /* Context */ 360 public UonSerializerBuilder builder() { 361 return new UonSerializerBuilder(getPropertyStore()); 362 } 363 364 /** 365 * Instantiates a new clean-slate {@link UonSerializerBuilder} object. 366 * 367 * <p> 368 * This is equivalent to simply calling <code><jk>new</jk> UonSerializerBuilder()</code>. 369 * 370 * <p> 371 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 372 * the settings of the object called on. 373 * 374 * @return A new {@link UonSerializerBuilder} object. 375 */ 376 public static UonSerializerBuilder create() { 377 return new UonSerializerBuilder(); 378 } 379 380 381 //----------------------------------------------------------------------------------------------------------------- 382 // Entry point methods 383 //----------------------------------------------------------------------------------------------------------------- 384 385 @Override /* Context */ 386 public UonSerializerSession createSession() { 387 return createSession(createDefaultSessionArgs()); 388 } 389 390 @Override /* Serializer */ 391 public UonSerializerSession createSession(SerializerSessionArgs args) { 392 return new UonSerializerSession(this, null, args); 393 } 394 395 @Override /* HttpPartSerializer */ 396 public UonSerializerSession createPartSession(SerializerSessionArgs args) { 397 return new UonSerializerSession(this, null, args); 398 } 399 400 @Override /* HttpPartSerializer */ 401 public UonSerializerSession createPartSession() { 402 return createPartSession(null); 403 } 404 405 @Override /* HttpPartSerializer */ 406 public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException { 407 return createPartSession().serialize(partType, schema, value); 408 } 409 410 @Override /* HttpPartSerializer */ 411 public String serialize(HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException { 412 return createPartSession().serialize(null, schema, value); 413 } 414 415 //----------------------------------------------------------------------------------------------------------------- 416 // Properties 417 //----------------------------------------------------------------------------------------------------------------- 418 419 /** 420 * Configuration property: Add <js>"_type"</js> properties when needed. 421 * 422 * @see #UON_addBeanTypes 423 * @return 424 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 425 * through reflection. 426 */ 427 @Override 428 protected final boolean isAddBeanTypes() { 429 return addBeanTypes; 430 } 431 432 /** 433 * Configuration property: Encode non-valid URI characters. 434 * 435 * @see #UON_encoding 436 * @return 437 * <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs. 438 */ 439 protected final boolean isEncoding() { 440 return encoding; 441 } 442 443 /** 444 * Configuration property: Format to use for query/form-data/header values. 445 * 446 * @see #UON_paramFormat 447 * @return 448 * Specifies the format to use for URL GET parameter keys and values. 449 */ 450 protected final ParamFormat getParamFormat() { 451 return paramFormat; 452 } 453 454 //----------------------------------------------------------------------------------------------------------------- 455 // Other methods 456 //----------------------------------------------------------------------------------------------------------------- 457 458 @Override /* Context */ 459 public ObjectMap toMap() { 460 return super.toMap() 461 .append("UonSerializer", new DefaultFilteringObjectMap() 462 .append("encoding", encoding) 463 .append("addBeanTypes", addBeanTypes) 464 .append("paramFormat", paramFormat) 465 ); 466 } 467}