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.serializer.*; 017import org.apache.juneau.urlencoding.*; 018 019/** 020 * Serializes POJO models to UON (a notation for URL-encoded query parameter values). 021 * 022 * <h5 class='topic'>Media types</h5> 023 * 024 * Handles <code>Accept</code> types: <code><b>text/uon</b></code> 025 * <p> 026 * Produces <code>Content-Type</code> types: <code><b>text/uon</b></code> 027 * 028 * <h5 class='topic'>Description</h5> 029 * 030 * This serializer provides several serialization options. 031 * Typically, one of the predefined DEFAULT serializers will be sufficient. 032 * 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'> 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 UON notation would be as follows: 061 * <p class='bcode'> 062 * ( 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 * ) 082 * </p> 083 * 084 * <h5 class='section'>Example:</h5> 085 * <p class='bcode'> 086 * <jc>// Serialize a Map</jc> 087 * Map m = <jk>new</jk> ObjectMap(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>); 088 * 089 * <jc>// Serialize to value equivalent to JSON.</jc> 090 * <jc>// Produces "(a=b,c=1,d=false,e=@(f,1,false),g=(h=i))"</jc> 091 * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); 092 * 093 * <jc>// Serialize a bean</jc> 094 * <jk>public class</jk> Person { 095 * <jk>public</jk> Person(String s); 096 * <jk>public</jk> String getName(); 097 * <jk>public int</jk> getAge(); 098 * <jk>public</jk> Address getAddress(); 099 * <jk>public boolean</jk> deceased; 100 * } 101 * 102 * <jk>public class</jk> Address { 103 * <jk>public</jk> String getStreet(); 104 * <jk>public</jk> String getCity(); 105 * <jk>public</jk> String getState(); 106 * <jk>public int</jk> getZip(); 107 * } 108 * 109 * Person p = <jk>new</jk> Person(<js>"John Doe"</js>, 23, <js>"123 Main St"</js>, <js>"Anywhere"</js>, 110 * <js>"NY"</js>, 12345, <jk>false</jk>); 111 * 112 * <jc>// Produces "(name='John Doe',age=23,address=(street='123 Main St',city=Anywhere,state=NY,zip=12345),deceased=false)"</jc> 113 * String s = UonSerializer.<jsf>DEFAULT</jsf>.serialize(s); 114 * </p> 115 */ 116public class UonSerializer extends WriterSerializer { 117 118 //------------------------------------------------------------------------------------------------------------------- 119 // Configurable properties 120 //------------------------------------------------------------------------------------------------------------------- 121 122 private static final String PREFIX = "UonSerializer."; 123 124 /** 125 * Configuration property: Add <js>"_type"</js> properties when needed. 126 * 127 * <h5 class='section'>Property:</h5> 128 * <ul> 129 * <li><b>Name:</b> <js>"UonSerializer.addBeanTypeProperties.b"</js> 130 * <li><b>Data type:</b> <code>Boolean</code> 131 * <li><b>Default:</b> <jk>true</jk> 132 * <li><b>Session-overridable:</b> <jk>true</jk> 133 * <li><b>Methods:</b> 134 * <ul> 135 * <li class='jm'>{@link UonSerializerBuilder#addBeanTypeProperties(boolean)} 136 * </ul> 137 * </ul> 138 * 139 * <h5 class='section'>Description:</h5> 140 * <p> 141 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 142 * through reflection. 143 * 144 * <p> 145 * When present, this value overrides the {@link #SERIALIZER_addBeanTypeProperties} setting and is 146 * provided to customize the behavior of specific serializers in a {@link SerializerGroup}. 147 */ 148 public static final String UON_addBeanTypeProperties = PREFIX + "addBeanTypeProperties.b"; 149 150 /** 151 * Configuration property: Encode non-valid URI characters. 152 * 153 * <h5 class='section'>Property:</h5> 154 * <ul> 155 * <li><b>Name:</b> <js>"UonSerializer.encoding.b"</js> 156 * <li><b>Data type:</b> <code>Boolean</code> 157 * <li><b>Default:</b> <jk>false</jk> for {@link UonSerializer}, <jk>true</jk> for {@link UrlEncodingSerializer} 158 * <li><b>Session-overridable:</b> <jk>true</jk> 159 * <li><b>Methods:</b> 160 * <ul> 161 * <li class='jm'>{@link UonSerializerBuilder#encoding(boolean)} 162 * <li class='jm'>{@link UonSerializerBuilder#encoding()} 163 * </ul> 164 * </ul> 165 * 166 * <h5 class='section'>Description:</h5> 167 * <p> 168 * Encode non-valid URI characters with <js>"%xx"</js> constructs. 169 * 170 * <p> 171 * If <jk>true</jk>, non-valid URI characters will be converted to <js>"%xx"</js> sequences. 172 * <br>Set to <jk>false</jk> if parameter value is being passed to some other code that will already perform 173 * URL-encoding of non-valid URI characters. 174 * 175 * <h5 class='section'>Example:</h5> 176 * <p class='bcode'> 177 * <jc>// Create a non-encoding UON serializer.</jc> 178 * UonSerializer s1 = UonSerializer. 179 * .<jsm>create</jsm>() 180 * .build(); 181 * 182 * <jc>// Create an encoding UON serializer.</jc> 183 * UonSerializer s2 = UonSerializer. 184 * .<jsm>create</jsm>() 185 * .encoding() 186 * .build(); 187 * 188 * ObjectMap m = <jk>new</jk> ObjectMap().append("foo", "foo bar"); 189 * 190 * <jc>// Produces: "(foo=foo bar)"</jc> 191 * String uon1 = s1.serialize(m) 192 * 193 * <jc>// Produces: "(foo=foo%20bar)"</jc> 194 * String uon2 = s2.serialize(m) 195 * </p> 196 */ 197 public static final String UON_encoding = PREFIX + "encoding.b"; 198 199 /** 200 * Configuration property: Format to use for query/form-data/header values. 201 * 202 * <h5 class='section'>Property:</h5> 203 * <ul> 204 * <li><b>Name:</b> <js>"UrlEncodingSerializer.paramFormat.s"</js> 205 * <li><b>Data type:</b> <code>String</code> ({@link ParamFormat}) 206 * <li><b>Default:</b> <js>"UON"</js> 207 * <li><b>Session-overridable:</b> <jk>true</jk> 208 * <li><b>Methods:</b> 209 * <ul> 210 * <li class='jm'>{@link UonSerializerBuilder#paramFormat(ParamFormat)} 211 * <li class='jm'>{@link UonSerializerBuilder#paramFormatPlain()} 212 * </ul> 213 * </ul> 214 * 215 * <h5 class='section'>Description:</h5> 216 * <p> 217 * Specifies the format to use for URL GET parameter keys and values. 218 * 219 * <p> 220 * Possible values: 221 * <ul> 222 * <li class='jf'>{@link ParamFormat#UON} - Use UON notation for parameters. 223 * <li class='jf'>{@link ParamFormat#PLAINTEXT} - Use plain text for parameters. 224 * </ul> 225 * 226 * <h5 class='section'>Example:</h5> 227 * <p class='bcode'> 228 * <jc>// Create a normal UON serializer.</jc> 229 * UonSerializer s1 = UonSerializer. 230 * .<jsm>create</jsm>() 231 * .build(); 232 * 233 * <jc>// Create a plain-text UON serializer.</jc> 234 * UonSerializer s2 = UonSerializer. 235 * .<jsm>create</jsm>() 236 * .paramFormat(<jsf>PLAIN_TEXT</jsf>) 237 * .build(); 238 * 239 * ObjectMap m = <jk>new</jk> ObjectMap() 240 * .append(<js>"foo"</js>, <js>"bar"</js>); 241 * .append(<js>"baz"</js>, <jk>new</jk> String[]{<js>"qux"</js>, <js>"true"</js>, <js>"123"</js>}); 242 * 243 * <jc>// Produces: "(foo=bar,baz=@(qux,'true','123'))"</jc> 244 * String uon1 = s1.serialize(m) 245 * 246 * <jc>// Produces: "foo=bar,baz=qux,true,123"</jc> 247 * String uon2 = s2.serialize(m) 248 * </p> 249 */ 250 public static final String UON_paramFormat = PREFIX + "paramFormat.s"; 251 252 253 //------------------------------------------------------------------------------------------------------------------- 254 // Predefined instances 255 //------------------------------------------------------------------------------------------------------------------- 256 257 /** Reusable instance of {@link UonSerializer}, all default settings. */ 258 public static final UonSerializer DEFAULT = new UonSerializer(PropertyStore.DEFAULT); 259 260 /** Reusable instance of {@link UonSerializer.Readable}. */ 261 public static final UonSerializer DEFAULT_READABLE = new Readable(PropertyStore.DEFAULT); 262 263 /** Reusable instance of {@link UonSerializer.Encoding}. */ 264 public static final UonSerializer DEFAULT_ENCODING = new Encoding(PropertyStore.DEFAULT); 265 266 267 //------------------------------------------------------------------------------------------------------------------- 268 // Predefined subclasses 269 //------------------------------------------------------------------------------------------------------------------- 270 271 /** 272 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().ws().build();</code>. 273 */ 274 public static class Readable extends UonSerializer { 275 276 /** 277 * Constructor. 278 * 279 * @param ps The property store containing all the settings for this object. 280 */ 281 public Readable(PropertyStore ps) { 282 super(ps.builder().set(SERIALIZER_useWhitespace, true).build()); 283 } 284 } 285 286 /** 287 * Equivalent to <code>UonSerializer.<jsm>create</jsm>().encoding().build();</code>. 288 */ 289 public static class Encoding extends UonSerializer { 290 291 /** 292 * Constructor. 293 * 294 * @param ps The property store containing all the settings for this object. 295 */ 296 public Encoding(PropertyStore ps) { 297 super(ps.builder().set(UON_encoding, true).build()); 298 } 299 } 300 301 302 //------------------------------------------------------------------------------------------------------------------- 303 // Instance 304 //------------------------------------------------------------------------------------------------------------------- 305 306 final boolean 307 encodeChars, 308 addBeanTypeProperties; 309 310 final ParamFormat 311 paramFormat; 312 313 /** 314 * Constructor. 315 * 316 * @param ps 317 * The property store containing all the settings for this object. 318 */ 319 public UonSerializer(PropertyStore ps) { 320 this(ps, "text/uon"); 321 } 322 323 /** 324 * Constructor. 325 * 326 * @param ps 327 * The property store containing all the settings for this object. 328 * @param produces 329 * The media type that this serializer produces. 330 * @param accept 331 * The accept media types that the serializer can handle. 332 * <p> 333 * Can contain meta-characters per the <code>media-type</code> specification of 334 * <a class="doclink" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">RFC2616/14.1</a> 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'> 341 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json"</js>, <js>"text/json"</js>); 342 * </p> 343 * <br>...or... 344 * <p class='bcode'> 345 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 346 * </p> 347 */ 348 public UonSerializer(PropertyStore ps, String produces, String...accept) { 349 super(ps, produces, accept); 350 encodeChars = getBooleanProperty(UON_encoding, false); 351 addBeanTypeProperties = getBooleanProperty(UON_addBeanTypeProperties, getBooleanProperty(SERIALIZER_addBeanTypeProperties, true)); 352 paramFormat = getProperty(UON_paramFormat, ParamFormat.class, ParamFormat.UON); 353 } 354 355 @Override /* Context */ 356 public UonSerializerBuilder builder() { 357 return new UonSerializerBuilder(getPropertyStore()); 358 } 359 360 /** 361 * Instantiates a new clean-slate {@link UonSerializerBuilder} object. 362 * 363 * <p> 364 * This is equivalent to simply calling <code><jk>new</jk> UonSerializerBuilder()</code>. 365 * 366 * <p> 367 * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies 368 * the settings of the object called on. 369 * 370 * @return A new {@link UonSerializerBuilder} object. 371 */ 372 public static UonSerializerBuilder create() { 373 return new UonSerializerBuilder(); 374 } 375 376 377 //-------------------------------------------------------------------------------- 378 // Entry point methods 379 //-------------------------------------------------------------------------------- 380 381 @Override /* Serializer */ 382 public WriterSerializerSession createSession(SerializerSessionArgs args) { 383 return new UonSerializerSession(this, null, args); 384 } 385 386 @Override /* Context */ 387 public ObjectMap asMap() { 388 return super.asMap() 389 .append("UonSerializer", new ObjectMap() 390 .append("encodeChars", encodeChars) 391 .append("addBeanTypeProperties", addBeanTypeProperties) 392 .append("paramFormat", paramFormat) 393 ); 394 } 395}