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.serializer; 014 015import java.io.*; 016 017import org.apache.juneau.*; 018import org.apache.juneau.annotation.*; 019import org.apache.juneau.http.*; 020import org.apache.juneau.internal.*; 021 022/** 023 * Parent class for all Juneau serializers. 024 * 025 * <h5 class='topic'>Description</h5> 026 * 027 * Base serializer class that serves as the parent class for all serializers. 028 * 029 * <p> 030 * The purpose of this class is: 031 * <ul> 032 * <li>Maintain a read-only configuration state of a serializer. 033 * <li>Create session objects used for serializing POJOs (i.e. {@link SerializerSession}). 034 * <li>Provide convenience methods for serializing POJOs without having to construct session objects. 035 * </ul> 036 * 037 * <p> 038 * Subclasses should extend directly from {@link OutputStreamSerializer} or {@link WriterSerializer} depending on 039 * whether it's a stream or character based serializer. 040 */ 041public abstract class Serializer extends BeanTraverseContext { 042 043 //------------------------------------------------------------------------------------------------------------------- 044 // Configurable properties 045 //------------------------------------------------------------------------------------------------------------------- 046 047 private static final String PREFIX = "Serializer."; 048 049 /** 050 * Configuration property: Add <js>"_type"</js> properties when needed. 051 * 052 * <h5 class='section'>Property:</h5> 053 * <ul> 054 * <li><b>Name:</b> <js>"Serializer.addBeanTypes.b"</js> 055 * <li><b>Data type:</b> <code>Boolean</code> 056 * <li><b>Default:</b> <jk>false</jk> 057 * <li><b>Session property:</b> <jk>false</jk> 058 * <li><b>Methods:</b> 059 * <ul> 060 * <li class='jm'>{@link SerializerBuilder#addBeanTypes()} 061 * <li class='jm'>{@link SerializerBuilder#addBeanTypes(boolean)} 062 * </ul> 063 * </ul> 064 * 065 * <h5 class='section'>Description:</h5> 066 * <p> 067 * If <jk>true</jk>, then <js>"_type"</js> properties will be added to beans if their type cannot be inferred 068 * through reflection. 069 * 070 * <p> 071 * This is used to recreate the correct objects during parsing if the object types cannot be inferred. 072 * <br>For example, when serializing a <code>Map<String,Object></code> field where the bean class cannot be determined from 073 * the type of the values. 074 * 075 * <p> 076 * Note the differences between the following settings: 077 * <ul> 078 * <li class='jf'>{@link #SERIALIZER_addRootType} - Affects whether <js>'_type'</js> is added to root node. 079 * <li class='jf'>{@link #SERIALIZER_addBeanTypes} - Affects whether <js>'_type'</js> is added to any nodes. 080 * </ul> 081 * 082 * <h5 class='section'>Example:</h5> 083 * <p class='bcode w800'> 084 * <jc>// Create a serializer that adds _type to nodes.</jc> 085 * WriterSerializer s = JsonSerializer 086 * .<jsm>create</jsm>() 087 * .addBeanTypes() 088 * .build(); 089 * 090 * <jc>// Same, but use property.</jc> 091 * WriterSerializer s = JsonSerializer 092 * .<jsm>create</jsm>() 093 * .set(<jsf>SERIALIZER_addBeanTypes</jsf>, <jk>true</jk>) 094 * .build(); 095 * 096 * <jc>// A map of objects we want to serialize.</jc> 097 * <ja>@Bean</ja>(typeName=<js>"mybean"</js>) 098 * <jk>public class</jk> MyBean {...} 099 * 100 * Map<String,Object> m = new HashMap<>(); 101 * m.put(<js>"foo"</js>, <jk>new</jk> MyBean()); 102 * 103 * <jc>// Will contain '_type' attribute.</jc> 104 * String json = s.serialize(m); 105 * </p> 106 */ 107 public static final String SERIALIZER_addBeanTypes = PREFIX + "addBeanTypes.b"; 108 109 /** 110 * Configuration property: Add type attribute to root nodes. 111 * 112 * <h5 class='section'>Property:</h5> 113 * <ul> 114 * <li><b>Name:</b> <js>"Serializer.addRootType.b"</js> 115 * <li><b>Data type:</b> <code>Boolean</code> 116 * <li><b>Default:</b> <jk>false</jk> 117 * <li><b>Session property:</b> <jk>false</jk> 118 * <li><b>Methods:</b> 119 * <ul> 120 * <li class='jm'>{@link SerializerBuilder#addRootType(boolean)} 121 * <li class='jm'>{@link SerializerBuilder#addRootType()} 122 * </ul> 123 * </ul> 124 * 125 * <h5 class='section'>Description:</h5> 126 * <p> 127 * When disabled, it is assumed that the parser knows the exact Java POJO type being parsed, and therefore top-level 128 * type information that might normally be included to determine the data type will not be serialized. 129 * 130 * <p> 131 * For example, when serializing a top-level POJO with a {@link Bean#typeName() @Bean(typeName)} value, a 132 * <js>'_type'</js> attribute will only be added when this setting is enabled. 133 * 134 * <p> 135 * Note the differences between the following settings: 136 * <ul> 137 * <li class='jf'>{@link #SERIALIZER_addRootType} - Affects whether <js>'_type'</js> is added to root node. 138 * <li class='jf'>{@link #SERIALIZER_addBeanTypes} - Affects whether <js>'_type'</js> is added to any nodes. 139 * </ul> 140 * 141 * <h5 class='section'>Example:</h5> 142 * <p class='bcode w800'> 143 * <jc>// Create a serializer that adds _type to root node.</jc> 144 * WriterSerializer s = JsonSerializer 145 * .<jsm>create</jsm>() 146 * .addRootType() 147 * .build(); 148 * 149 * <jc>// Same, but use property.</jc> 150 * WriterSerializer s = JsonSerializer 151 * .<jsm>create</jsm>() 152 * .set(<jsf>SERIALIZER_addRootType</jsf>, <jk>true</jk>) 153 * .build(); 154 * 155 * <jc>// The bean we want to serialize.</jc> 156 * <ja>@Bean</ja>(typeName=<js>"mybean"</js>) 157 * <jk>public class</jk> MyBean {...} 158 * 159 * <jc>// Will contain '_type' attribute.</jc> 160 * String json = s.serialize(<jk>new</jk> MyBean()); 161 * </p> 162 */ 163 public static final String SERIALIZER_addRootType = PREFIX + "addRootType.b"; 164 165 /** 166 * Configuration property: Serializer listener. 167 * 168 * <h5 class='section'>Property:</h5> 169 * <ul> 170 * <li><b>Name:</b> <js>"Serializer.listener.c"</js> 171 * <li><b>Data type:</b> <code>Class<? extends SerializerListener></code> 172 * <li><b>Default:</b> <jk>null</jk> 173 * <li><b>Session property:</b> <jk>false</jk> 174 * <li><b>Methods:</b> 175 * <ul> 176 * <li class='jm'>{@link SerializerBuilder#listener(Class)} 177 * </ul> 178 * </ul> 179 * 180 * <h5 class='section'>Description:</h5> 181 * <p> 182 * Class used to listen for errors and warnings that occur during serialization. 183 * 184 * <h5 class='section'>Example:</h5> 185 * <p class='bcode w800'> 186 * <jc>// Define our serializer listener.</jc> 187 * <jc>// Simply captures all errors.</jc> 188 * <jk>public class</jk> MySerializerListener <jk>extends</jk> SerializerListener { 189 * 190 * <jc>// A simple property to store our events.</jc> 191 * <jk>public</jk> List<String> <jf>events</jf> = <jk>new</jk> LinkedList<>(); 192 * 193 * <ja>@Override</ja> 194 * <jk>public</jk> <T> <jk>void</jk> onError(SerializerSession session, Throwable t, String msg) { 195 * <jf>events</jf>.add(session.getLastLocation() + <js>","</js> + msg + <js>","</js> + t); 196 * } 197 * } 198 * 199 * <jc>// Create a serializer using our listener.</jc> 200 * WriterSerializer s = JsonSerializer. 201 * .<jsm>create</jsm>() 202 * .listener(MySerializerListener.<jk>class</jk>) 203 * .build(); 204 * 205 * <jc>// Same, but use property.</jc> 206 * WriterSerializer s = JsonSerializer. 207 * .<jsm>create</jsm>() 208 * .set(<jsf>SERIALIZER_listener</jsf>, MySerializerListener.<jk>class</jk>) 209 * .build(); 210 * 211 * <jc>// Create a session object.</jc> 212 * <jc>// Needed because listeners are created per-session.</jc> 213 * <jk>try</jk> (WriterSerializerSession ss = s.createSession()) { 214 * 215 * <jc>// Serialize a bean.</jc> 216 * String json = ss.serialize(<jk>new</jk> MyBean()); 217 * 218 * <jc>// Get the listener.</jc> 219 * MySerializerListener l = ss.getListener(MySerializerListener.<jk>class</jk>); 220 * 221 * <jc>// Dump the results to the console.</jc> 222 * SimpleJsonSerializer.<jsf>DEFAULT</jsf>.println(l.<jf>events</jf>); 223 * } 224 * </p> 225 */ 226 public static final String SERIALIZER_listener = PREFIX + "listener.c"; 227 228 /** 229 * Configuration property: Sort arrays and collections alphabetically. 230 * 231 * <h5 class='section'>Property:</h5> 232 * <ul> 233 * <li><b>Name:</b> <js>"Serializer.sortCollections.b"</js> 234 * <li><b>Data type:</b> <code>Boolean</code> 235 * <li><b>Default:</b> <jk>false</jk> 236 * <li><b>Session property:</b> <jk>false</jk> 237 * <li><b>Methods:</b> 238 * <ul> 239 * <li class='jm'>{@link SerializerBuilder#sortCollections(boolean)} 240 * <li class='jm'>{@link SerializerBuilder#sortCollections()} 241 * </ul> 242 * </ul> 243 * 244 * <h5 class='section'>Description:</h5> 245 * 246 * <p> 247 * Copies and sorts the contents of arrays and collections before serializing them. 248 * 249 * <p> 250 * Note that this introduces a performance penalty. 251 * 252 * <h5 class='section'>Example:</h5> 253 * <p class='bcode w800'> 254 * <jc>// Create a serializer that sorts arrays and collections before serialization.</jc> 255 * WriterSerializer s = JsonSerializer 256 * .<jsm>create</jsm>() 257 * .sortCollections() 258 * .build(); 259 * 260 * <jc>// Same, but use property.</jc> 261 * WriterSerializer s = JsonSerializer 262 * .<jsm>create</jsm>() 263 * .set(<jsf>SERIALIZER_sortCollections</jsf>, <jk>true</jk>) 264 * .build(); 265 * </p> 266 */ 267 public static final String SERIALIZER_sortCollections = PREFIX + "sortCollections.b"; 268 269 /** 270 * Configuration property: Sort maps alphabetically. 271 * 272 * <h5 class='section'>Property:</h5> 273 * <ul> 274 * <li><b>Name:</b> <js>"Serializer.sortMaps.b"</js> 275 * <li><b>Data type:</b> <code>Boolean</code> 276 * <li><b>Default:</b> <jk>false</jk> 277 * <li><b>Session property:</b> <jk>false</jk> 278 * <li><b>Methods:</b> 279 * <ul> 280 * <li class='jm'>{@link SerializerBuilder#sortMaps(boolean)} 281 * <li class='jm'>{@link SerializerBuilder#sortMaps()} 282 * </ul> 283 * </ul> 284 * 285 * <h5 class='section'>Description:</h5> 286 * 287 * <p> 288 * Copies and sorts the contents of maps by their keys before serializing them. 289 * 290 * <p> 291 * Note that this introduces a performance penalty. 292 * 293 * <h5 class='section'>Example:</h5> 294 * <p class='bcode w800'> 295 * <jc>// Create a serializer that sorts maps before serialization.</jc> 296 * WriterSerializer s = JsonSerializer 297 * .<jsm>create</jsm>() 298 * .sortMaps() 299 * .build(); 300 * 301 * <jc>// Same, but use property.</jc> 302 * WriterSerializer s = JsonSerializer 303 * .<jsm>create</jsm>() 304 * .set(<jsf>SERIALIZER_sortMaps</jsf>, <jk>true</jk>) 305 * .build(); 306 * </p> 307 */ 308 public static final String SERIALIZER_sortMaps = PREFIX + "sortMaps.b"; 309 310 /** 311 * Configuration property: Trim empty lists and arrays. 312 * 313 * <h5 class='section'>Property:</h5> 314 * <ul> 315 * <li><b>Name:</b> <js>"Serializer.trimEmptyCollections.b"</js> 316 * <li><b>Data type:</b> <code>Boolean</code> 317 * <li><b>Default:</b> <jk>false</jk> 318 * <li><b>Session property:</b> <jk>false</jk> 319 * <li><b>Methods:</b> 320 * <ul> 321 * <li class='jm'>{@link SerializerBuilder#trimEmptyCollections(boolean)} 322 * <li class='jm'>{@link SerializerBuilder#trimEmptyCollections()} 323 * </ul> 324 * </ul> 325 * 326 * <h5 class='section'>Description:</h5> 327 * 328 * <p> 329 * If <jk>true</jk>, empty lists and arrays will not be serialized. 330 * 331 * <p> 332 * Note that enabling this setting has the following effects on parsing: 333 * <ul class='spaced-list'> 334 * <li> 335 * Map entries with empty list values will be lost. 336 * <li> 337 * Bean properties with empty list values will not be set. 338 * </ul> 339 * 340 * <h5 class='section'>Example:</h5> 341 * <p class='bcode w800'> 342 * <jc>// Create a serializer that skips empty arrays and collections.</jc> 343 * WriterSerializer s = JsonSerializer 344 * .<jsm>create</jsm>() 345 * .trimEmptyCollections() 346 * .build(); 347 * 348 * <jc>// Same, but use property.</jc> 349 * WriterSerializer s = JsonSerializer 350 * .<jsm>create</jsm>() 351 * .set(<jsf>SERIALIZER_trimEmptyCollections</jsf>, <jk>true</jk>) 352 * .build(); 353 * </p> 354 */ 355 public static final String SERIALIZER_trimEmptyCollections = PREFIX + "trimEmptyCollections.b"; 356 357 /** 358 * Configuration property: Trim empty maps. 359 * 360 * <h5 class='section'>Property:</h5> 361 * <ul> 362 * <li><b>Name:</b> <js>"Serializer.trimEmptyMaps.b"</js> 363 * <li><b>Data type:</b> <code>Boolean</code> 364 * <li><b>Default:</b> <jk>false</jk> 365 * <li><b>Session property:</b> <jk>false</jk> 366 * <li><b>Methods:</b> 367 * <ul> 368 * <li class='jm'>{@link SerializerBuilder#trimEmptyMaps(boolean)} 369 * <li class='jm'>{@link SerializerBuilder#trimEmptyMaps()} 370 * </ul> 371 * </ul> 372 * 373 * <h5 class='section'>Description:</h5> 374 * <p> 375 * If <jk>true</jk>, empty map values will not be serialized to the output. 376 * 377 * <p> 378 * Note that enabling this setting has the following effects on parsing: 379 * <ul class='spaced-list'> 380 * <li> 381 * Bean properties with empty map values will not be set. 382 * </ul> 383 * 384 * <h5 class='section'>Example:</h5> 385 * <p class='bcode w800'> 386 * <jc>// Create a serializer that skips empty maps.</jc> 387 * WriterSerializer s = JsonSerializer 388 * .<jsm>create</jsm>() 389 * .trimEmptyMaps() 390 * .build(); 391 * 392 * <jc>// Same, but use property.</jc> 393 * WriterSerializer s = JsonSerializer 394 * .<jsm>create</jsm>() 395 * .set(<jsf>SERIALIZER_trimEmptyMaps</jsf>, <jk>true</jk>) 396 * .build(); 397 * </p> 398 */ 399 public static final String SERIALIZER_trimEmptyMaps = PREFIX + "trimEmptyMaps.b"; 400 401 /** 402 * Configuration property: Trim null bean property values. 403 * 404 * <h5 class='section'>Property:</h5> 405 * <ul> 406 * <li><b>Name:</b> <js>"Serializer.trimNullProperties.b"</js> 407 * <li><b>Data type:</b> <code>Boolean</code> 408 * <li><b>Default:</b> <jk>true</jk> 409 * <li><b>Session property:</b> <jk>false</jk> 410 * <li><b>Methods:</b> 411 * <ul> 412 * <li class='jm'>{@link SerializerBuilder#trimNullProperties(boolean)} 413 * </ul> 414 * </ul> 415 * 416 * <h5 class='section'>Description:</h5> 417 * <p> 418 * If <jk>true</jk>, null bean values will not be serialized to the output. 419 * 420 * <p> 421 * Note that enabling this setting has the following effects on parsing: 422 * <ul class='spaced-list'> 423 * <li> 424 * Map entries with <jk>null</jk> values will be lost. 425 * </ul> 426 * 427 * <h5 class='section'>Example:</h5> 428 * <p class='bcode w800'> 429 * <jc>// Create a serializer that serializes null properties.</jc> 430 * WriterSerializer s = JsonSerializer 431 * .<jsm>create</jsm>() 432 * .trimNullProperties(<jk>false</jk>) 433 * .build(); 434 * 435 * <jc>// Same, but use property.</jc> 436 * WriterSerializer s = JsonSerializer 437 * .<jsm>create</jsm>() 438 * .set(<jsf>SERIALIZER_trimNullProperties</jsf>, <jk>false</jk>) 439 * .build(); 440 * </p> 441 */ 442 public static final String SERIALIZER_trimNullProperties = PREFIX + "trimNullProperties.b"; 443 444 /** 445 * Configuration property: Trim strings. 446 * 447 * <h5 class='section'>Property:</h5> 448 * <ul> 449 * <li><b>Name:</b> <js>"Serializer.trimStrings.b"</js> 450 * <li><b>Data type:</b> <code>Boolean</code> 451 * <li><b>Default:</b> <jk>false</jk> 452 * <li><b>Session property:</b> <jk>false</jk> 453 * <li><b>Methods:</b> 454 * <ul> 455 * <li class='jm'>{@link SerializerBuilder#trimStrings(boolean)} 456 * <li class='jm'>{@link SerializerBuilder#trimStrings()} 457 * </ul> 458 * </ul> 459 * 460 * <h5 class='section'>Description:</h5> 461 * <p> 462 * If <jk>true</jk>, string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 463 * 464 * <h5 class='section'>Example:</h5> 465 * <p class='bcode w800'> 466 * <jc>// Create a serializer that trims strings before serialization.</jc> 467 * WriterSerializer s = JsonSerializer 468 * .<jsm>create</jsm>() 469 * .trimStrings() 470 * .build(); 471 * 472 * <jc>// Same, but use property.</jc> 473 * WriterSerializer s = JsonSerializer 474 * .<jsm>create</jsm>() 475 * .set(<jsf>SERIALIZER_trimStrings</jsf>, <jk>true</jk>) 476 * .build(); 477 * 478 * Map<String,String> m = <jk>new</jk> HashMap<>(); 479 * m.put(<js>" foo "</js>, <js>" bar "</js>); 480 * 481 * <jc>// Produces "{foo:'bar'}"</jc> 482 * String json = SimpleJsonSerializer.<jsf>DEFAULT</jsf>.toString(m); 483 * </p> 484 */ 485 public static final String SERIALIZER_trimStrings = PREFIX + "trimStrings.b"; 486 487 /** 488 * Configuration property: URI context bean. 489 * 490 * <h5 class='section'>Property:</h5> 491 * <ul> 492 * <li><b>Name:</b> <js>"Serializer.uriContext.s"</js> 493 * <li><b>Data type:</b> <code>String</code> (JSON object representing a {@link UriContext}) 494 * <li><b>Default:</b> <js>"{}"</js> 495 * <li><b>Session property:</b> <jk>true</jk> 496 * <li><b>Methods:</b> 497 * <ul> 498 * <li class='jm'>{@link SerializerBuilder#uriContext(UriContext)} 499 * <li class='jm'>{@link SerializerBuilder#uriContext(String)} 500 * </ul> 501 * </ul> 502 * 503 * <h5 class='section'>Description:</h5> 504 * <p> 505 * Bean used for resolution of URIs to absolute or root-relative form. 506 * 507 * <h5 class='section'>Example:</h5> 508 * <p class='bcode w800'> 509 * <jc>// Our URI contextual information.</jc> 510 * String authority = <js>"http://localhost:10000"</js>; 511 * String contextRoot = <js>"/myContext"</js>; 512 * String servletPath = <js>"/myServlet"</js>; 513 * String pathInfo = <js>"/foo"</js>; 514 * 515 * <jc>// Create a UriContext object.</jc> 516 * UriContext uriContext = <jk>new</jk> UriContext(authority, contextRoot, servletPath, pathInfo); 517 * 518 * <jc>// Associate it with our serializer.</jc> 519 * WriterSerializer s = JsonSerializer 520 * .<jsm>create</jsm>() 521 * .uriContext(uriContext) 522 * .build(); 523 * 524 * <jc>// Same, but specify as a JSON string.</jc> 525 * WriterSerializer s = JsonSerializer 526 * .<jsm>create</jsm>() 527 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 528 * .build(); 529 * 530 * <jc>// Same, but use property.</jc> 531 * WriterSerializer s = JsonSerializer 532 * .<jsm>create</jsm>() 533 * .set(<jsf>SERIALIZER_uriContext</jsf>, uriContext) 534 * .build(); 535 * 536 * <jc>// Same, but define it on the session args instead.</jc> 537 * SerializerSessionArgs sessionArgs = <jk>new</jk> SerializerSessionArgs().uriContext(uriContext); 538 * <jk>try</jk> (WriterSerializerSession session = s.createSession(sessionArgs)) { 539 * ... 540 * } 541 * </p> 542 */ 543 public static final String SERIALIZER_uriContext = PREFIX + "uriContext.s"; 544 545 /** 546 * Configuration property: URI relativity. 547 * 548 * <h5 class='section'>Property:</h5> 549 * <ul> 550 * <li><b>Name:</b> <js>"Serializer.uriRelativity.s"</js> 551 * <li><b>Data type:</b> <code>String</code> ({@link UriRelativity}) 552 * <li><b>Default:</b> <js>"RESOURCE"</js> 553 * <li><b>Session property:</b> <jk>false</jk> 554 * <li><b>Methods:</b> 555 * <ul> 556 * <li class='jm'>{@link SerializerBuilder#uriRelativity(UriRelativity)} 557 * <li class='jm'>{@link SerializerBuilder#uriRelativity(String)} 558 * </ul> 559 * </ul> 560 * 561 * <h5 class='section'>Description:</h5> 562 * <p> 563 * Defines what relative URIs are relative to when serializing any of the following: 564 * <ul> 565 * <li>{@link java.net.URI} 566 * <li>{@link java.net.URL} 567 * <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI} 568 * </ul> 569 * 570 * <p> 571 * Possible values are: 572 * <ul> 573 * <li class='jf'>{@link UriRelativity#RESOURCE} 574 * - Relative URIs should be considered relative to the servlet URI. 575 * <li class='jf'>{@link UriRelativity#PATH_INFO} 576 * - Relative URIs should be considered relative to the request URI. 577 * </ul> 578 * 579 * <h5 class='figure'>Example:</h5> 580 * <p class='bcode w800'> 581 * <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc> 582 * WriterSerializer s = JsonSerializer 583 * .<jsm>create</jsm>() 584 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 585 * .uriResolution(<jsf>ABSOLUTE</jsf>) 586 * .uriRelativity(<jsf>RESOURCE</jsf>) 587 * .build(); 588 * </p> 589 * 590 * <h5 class='section'>See Also:</h5> 591 * <ul> 592 * <li class='link'>{@doc juneau-marshall.URIs} 593 * </ul> 594 */ 595 public static final String SERIALIZER_uriRelativity = PREFIX + "uriRelativity.s"; 596 597 /** 598 * Configuration property: URI resolution. 599 * 600 * <h5 class='section'>Property:</h5> 601 * <ul> 602 * <li><b>Name:</b> <js>"Serializer.uriResolution.s"</js> 603 * <li><b>Data type:</b> <code>String</code> ({@link UriResolution}) 604 * <li><b>Default:</b> <js>"NONE"</js> 605 * <li><b>Session property:</b> <jk>false</jk> 606 * <li><b>Methods:</b> 607 * <ul> 608 * <li class='jm'>{@link SerializerBuilder#uriResolution(UriResolution)} 609 * <li class='jm'>{@link SerializerBuilder#uriResolution(String)} 610 * </ul> 611 * </ul> 612 * 613 * <h5 class='section'>Description:</h5> 614 * <p> 615 * Defines the resolution level for URIs when serializing any of the following: 616 * <ul> 617 * <li>{@link java.net.URI} 618 * <li>{@link java.net.URL} 619 * <li>Properties and classes annotated with {@link org.apache.juneau.annotation.URI @URI} 620 * </ul> 621 * 622 * <p> 623 * Possible values are: 624 * <ul> 625 * <li class='jf'>{@link UriResolution#ABSOLUTE} 626 * - Resolve to an absolute URL (e.g. <js>"http://host:port/context-root/servlet-path/path-info"</js>). 627 * <li class='jf'>{@link UriResolution#ROOT_RELATIVE} 628 * - Resolve to a root-relative URL (e.g. <js>"/context-root/servlet-path/path-info"</js>). 629 * <li class='jf'>{@link UriResolution#NONE} 630 * - Don't do any URL resolution. 631 * </ul> 632 * 633 * <h5 class='figure'>Example:</h5> 634 * <p class='bcode w800'> 635 * <jc>// Define a serializer that converts resource-relative URIs to absolute form.</jc> 636 * WriterSerializer s = JsonSerializer 637 * .<jsm>create</jsm>() 638 * .uriContext(<js>"{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}"</js>) 639 * .uriResolution(<jsf>ABSOLUTE</jsf>) 640 * .uriRelativity(<jsf>RESOURCE</jsf>) 641 * .build(); 642 * </p> 643 * 644 * <h5 class='section'>See Also:</h5> 645 * <ul> 646 * <li class='link'>{@doc juneau-marshall.URIs} 647 * </ul> 648 */ 649 public static final String SERIALIZER_uriResolution = PREFIX + "uriResolution.s"; 650 651 /** 652 * Configuration property: Use whitespace. 653 * 654 * <h5 class='section'>Property:</h5> 655 * <ul> 656 * <li><b>Name:</b> <js>"Serializer.useWhitespace.b"</js> 657 * <li><b>Data type:</b> <code>Boolean</code> 658 * <li><b>Default:</b> <jk>false</jk> 659 * <li><b>Session property:</b> <jk>true</jk> 660 * <li><b>Methods:</b> 661 * <ul> 662 * <li class='jm'>{@link SerializerBuilder#useWhitespace(boolean)} 663 * <li class='jm'>{@link SerializerBuilder#useWhitespace()} 664 * <li class='jm'>{@link SerializerBuilder#ws()} 665 * <li class='jm'>{@link SerializerSessionArgs#useWhitespace(Boolean)} 666 * </ul> 667 * </ul> 668 * 669 * <h5 class='section'>Description:</h5> 670 * <p> 671 * If <jk>true</jk>, whitespace is added to the output to improve readability. 672 * 673 * <h5 class='section'>Example:</h5> 674 * <p class='bcode w800'> 675 * <jc>// Create a serializer with whitespace enabled.</jc> 676 * WriterSerializer s = JsonSerializer 677 * .<jsm>create</jsm>() 678 * .ws() 679 * .build(); 680 * 681 * <jc>// Same, but use property.</jc> 682 * WriterSerializer s = JsonSerializer 683 * .<jsm>create</jsm>() 684 * .set(<jsf>SERIALIZER_useWhitespace</jsf>, <jk>true</jk>) 685 * .build(); 686 * 687 * <jc>// Produces "\{\n\t'foo': 'bar'\n\}\n"</jc> 688 * String json = s.serialize(<jk>new</jk> MyBean()); 689 * </p> 690 */ 691 public static final String SERIALIZER_useWhitespace = PREFIX + "useWhitespace.b"; 692 693 694 static final Serializer DEFAULT = new Serializer(PropertyStore.create().build(), "", "") { 695 @Override 696 public SerializerSession createSession(SerializerSessionArgs args) { 697 throw new NoSuchMethodError(); 698 } 699 }; 700 701 //------------------------------------------------------------------------------------------------------------------- 702 // Instance 703 //------------------------------------------------------------------------------------------------------------------- 704 705 private final boolean 706 addBeanTypes, 707 trimNullProperties, 708 trimEmptyCollections, 709 trimEmptyMaps, 710 trimStrings, 711 sortCollections, 712 sortMaps, 713 addRootType, 714 useWhitespace; 715 private final UriContext uriContext; 716 private final UriResolution uriResolution; 717 private final UriRelativity uriRelativity; 718 private final Class<? extends SerializerListener> listener; 719 720 private final MediaTypeRange[] accept; 721 private final MediaType[] accepts; 722 private final MediaType produces; 723 724 /** 725 * Constructor 726 * 727 * @param ps 728 * The property store containing all the settings for this object. 729 * @param produces 730 * The media type that this serializer produces. 731 * @param accept 732 * The accept media types that the serializer can handle. 733 * <p> 734 * Can contain meta-characters per the <code>media-type</code> specification of {@doc RFC2616.section14.1} 735 * <p> 736 * If empty, then assumes the only media type supported is <code>produces</code>. 737 * <p> 738 * For example, if this serializer produces <js>"application/json"</js> but should handle media types of 739 * <js>"application/json"</js> and <js>"text/json"</js>, then the arguments should be: 740 * <p class='bcode w800'> 741 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"application/json,text/json"</js>); 742 * </p> 743 * <br>...or... 744 * <p class='bcode w800'> 745 * <jk>super</jk>(ps, <js>"application/json"</js>, <js>"*​/json"</js>); 746 * </p> 747 * <p> 748 * The accept value can also contain q-values. 749 */ 750 protected Serializer(PropertyStore ps, String produces, String accept) { 751 super(ps); 752 753 addBeanTypes = getBooleanProperty(SERIALIZER_addBeanTypes, false); 754 trimNullProperties = getBooleanProperty(SERIALIZER_trimNullProperties, true); 755 trimEmptyCollections = getBooleanProperty(SERIALIZER_trimEmptyCollections, false); 756 trimEmptyMaps = getBooleanProperty(SERIALIZER_trimEmptyMaps, false); 757 trimStrings = getBooleanProperty(SERIALIZER_trimStrings, false); 758 sortCollections = getBooleanProperty(SERIALIZER_sortCollections, false); 759 sortMaps = getBooleanProperty(SERIALIZER_sortMaps, false); 760 addRootType = getBooleanProperty(SERIALIZER_addRootType, false); 761 uriContext = getProperty(SERIALIZER_uriContext, UriContext.class, UriContext.DEFAULT); 762 uriResolution = getProperty(SERIALIZER_uriResolution, UriResolution.class, UriResolution.NONE); 763 uriRelativity = getProperty(SERIALIZER_uriRelativity, UriRelativity.class, UriRelativity.RESOURCE); 764 useWhitespace = getBooleanProperty(SERIALIZER_useWhitespace, false); 765 listener = getClassProperty(SERIALIZER_listener, SerializerListener.class, null); 766 767 this.produces = MediaType.forString(produces); 768 this.accept = accept == null ? MediaTypeRange.parse(produces) : MediaTypeRange.parse(accept); 769 this.accepts = accept == null ? new MediaType[] {this.produces} : MediaType.forStrings(StringUtils.split(accept, ',')); 770 } 771 772 @Override /* Context */ 773 public SerializerBuilder builder() { 774 return null; 775 } 776 777 //----------------------------------------------------------------------------------------------------------------- 778 // Abstract methods 779 //----------------------------------------------------------------------------------------------------------------- 780 781 /** 782 * Returns <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 783 * 784 * @return <jk>true</jk> if this serializer subclasses from {@link WriterSerializer}. 785 */ 786 public boolean isWriterSerializer() { 787 return true; 788 } 789 790 /** 791 * Create the session object used for actual serialization of objects. 792 * 793 * @param args 794 * Runtime arguments. 795 * These specify session-level information such as locale and URI context. 796 * It also include session-level properties that override the properties defined on the bean and serializer 797 * contexts. 798 * @return 799 * The new session object. 800 */ 801 public abstract SerializerSession createSession(SerializerSessionArgs args); 802 803 804 //----------------------------------------------------------------------------------------------------------------- 805 // Convenience methods 806 //----------------------------------------------------------------------------------------------------------------- 807 808 @Override /* Context */ 809 public SerializerSession createSession() { 810 return createSession(createDefaultSessionArgs()); 811 } 812 813 @Override /* Context */ 814 public final SerializerSessionArgs createDefaultSessionArgs() { 815 return new SerializerSessionArgs().mediaType(getResponseContentType()); 816 } 817 818 /** 819 * Serializes a POJO to the specified output stream or writer. 820 * 821 * <p> 822 * Equivalent to calling <code>serializer.createSession().serialize(o, output);</code> 823 * 824 * @param o The object to serialize. 825 * @param output 826 * The output object. 827 * <br>Character-based serializers can handle the following output class types: 828 * <ul> 829 * <li>{@link Writer} 830 * <li>{@link OutputStream} - Output will be written as UTF-8 encoded stream. 831 * <li>{@link File} - Output will be written as system-default encoded stream. 832 * <li>{@link StringBuilder} - Output will be written to the specified string builder. 833 * </ul> 834 * <br>Stream-based serializers can handle the following output class types: 835 * <ul> 836 * <li>{@link OutputStream} 837 * <li>{@link File} 838 * </ul> 839 * @throws SerializeException If a problem occurred trying to convert the output. 840 */ 841 public final void serialize(Object o, Object output) throws SerializeException { 842 createSession().serialize(o, output); 843 } 844 845 /** 846 * Shortcut method for serializing objects directly to either a <code>String</code> or <code><jk>byte</jk>[]</code> 847 * depending on the serializer type. 848 * 849 * @param o The object to serialize. 850 * @return 851 * The serialized object. 852 * <br>Character-based serializers will return a <code>String</code> 853 * <br>Stream-based serializers will return a <code><jk>byte</jk>[]</code> 854 * @throws SerializeException If a problem occurred trying to convert the output. 855 */ 856 public Object serialize(Object o) throws SerializeException { 857 return createSession().serialize(o); 858 } 859 860 /** 861 * Convenience method for serializing an object to a String. 862 * 863 * <p> 864 * For writer-based serializers, this is identical to calling {@link #serialize(Object)}. 865 * <br>For stream-based serializers, this converts the returned byte array to a string based on 866 * the {@link OutputStreamSerializer#OSSERIALIZER_binaryFormat} setting. 867 * 868 * @param o The object to serialize. 869 * @return The output serialized to a string. 870 * @throws SerializeException If a problem occurred trying to convert the output. 871 */ 872 public final String serializeToString(Object o) throws SerializeException { 873 return createSession().serializeToString(o); 874 } 875 876 //----------------------------------------------------------------------------------------------------------------- 877 // Other methods 878 //----------------------------------------------------------------------------------------------------------------- 879 880 /** 881 * Returns the media types handled based on the value of the <code>accept</code> parameter passed into the constructor. 882 * 883 * <p> 884 * Note that the order of these ranges are from high to low q-value. 885 * 886 * @return The list of media types. Never <jk>null</jk>. 887 */ 888 public final MediaTypeRange[] getMediaTypeRanges() { 889 return accept; 890 } 891 892 /** 893 * Returns the first entry in the <code>accept</code> parameter passed into the constructor. 894 * 895 * <p> 896 * This signifies the 'primary' media type for this serializer. 897 * 898 * @return The media type. Never <jk>null</jk>. 899 */ 900 public final MediaType getPrimaryMediaType() { 901 return accepts[0]; 902 } 903 904 /** 905 * Returns the media types handled based on the value of the <code>accept</code> parameter passed into the constructor. 906 * 907 * <p> 908 * The order of the media types are the same as those in the <code>accept</code> parameter. 909 * 910 * @return The list of media types. Never <jk>null</jk>. 911 */ 912 public final MediaType[] getAcceptMediaTypes() { 913 return accepts; 914 } 915 916 /** 917 * Optional method that returns the response <code>Content-Type</code> for this serializer if it is different from 918 * the matched media type. 919 * 920 * <p> 921 * This method is specified to override the content type for this serializer. 922 * For example, the {@link org.apache.juneau.json.SimpleJsonSerializer} class returns that it handles media type 923 * <js>"text/json+simple"</js>, but returns <js>"text/json"</js> as the actual content type. 924 * This allows clients to request specific 'flavors' of content using specialized <code>Accept</code> header values. 925 * 926 * <p> 927 * This method is typically meaningless if the serializer is being used stand-alone (i.e. outside of a REST server 928 * or client). 929 * 930 * @return The response content type. If <jk>null</jk>, then the matched media type is used. 931 */ 932 public final MediaType getResponseContentType() { 933 return produces; 934 } 935 936 //----------------------------------------------------------------------------------------------------------------- 937 // Properties 938 //----------------------------------------------------------------------------------------------------------------- 939 940 /** 941 * Configuration property: Add <js>"_type"</js> properties when needed. 942 * 943 * @see #SERIALIZER_addBeanTypes 944 * @return 945 * <jk>true</jk> if <js>"_type"</js> properties added to beans if their type cannot be inferred 946 * through reflection. 947 */ 948 protected boolean isAddBeanTypes() { 949 return addBeanTypes; 950 } 951 952 /** 953 * Configuration property: Trim null bean property values. 954 * 955 * @see #SERIALIZER_trimNullProperties 956 * @return 957 * <jk>true</jk> if null bean values are not serialized to the output. 958 */ 959 protected final boolean isTrimNullProperties() { 960 return trimNullProperties; 961 } 962 963 /** 964 * Configuration property: Trim empty lists and arrays. 965 * 966 * @see #SERIALIZER_trimEmptyCollections 967 * @return 968 * <jk>true</jk> if empty lists and arrays are not serialized to the output. 969 */ 970 protected final boolean isTrimEmptyCollections() { 971 return trimEmptyCollections; 972 } 973 974 /** 975 * Configuration property: Trim empty maps. 976 * 977 * @see #SERIALIZER_trimEmptyMaps 978 * @return 979 * <jk>true</jk> if empty map values are not serialized to the output. 980 */ 981 protected final boolean isTrimEmptyMaps() { 982 return trimEmptyMaps; 983 } 984 985 /** 986 * Configuration property: Trim strings. 987 * 988 * @see #SERIALIZER_trimStrings 989 * @return 990 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 991 */ 992 protected final boolean isTrimStrings() { 993 return trimStrings; 994 } 995 996 /** 997 * Configuration property: Sort arrays and collections alphabetically. 998 * 999 * @see #SERIALIZER_sortCollections 1000 * @return 1001 * <jk>true</jk> if arrays and collections are copied and sorted before serialization. 1002 */ 1003 protected final boolean isSortCollections() { 1004 return sortCollections; 1005 } 1006 1007 /** 1008 * Configuration property: Sort maps alphabetically. 1009 * 1010 * @see #SERIALIZER_sortMaps 1011 * @return 1012 * <jk>true</jk> if maps are copied and sorted before serialization. 1013 */ 1014 protected final boolean isSortMaps() { 1015 return sortMaps; 1016 } 1017 1018 /** 1019 * Configuration property: Add type attribute to root nodes. 1020 * 1021 * @see #SERIALIZER_addRootType 1022 * @return 1023 * <jk>true</jk> if type property should be added to root node. 1024 */ 1025 protected final boolean isAddRootType() { 1026 return addRootType; 1027 } 1028 1029 /** 1030 * Configuration property: URI context bean. 1031 * 1032 * @see #SERIALIZER_uriContext 1033 * @return 1034 * Bean used for resolution of URIs to absolute or root-relative form. 1035 */ 1036 protected final UriContext getUriContext() { 1037 return uriContext; 1038 } 1039 1040 /** 1041 * Configuration property: URI resolution. 1042 * 1043 * @see #SERIALIZER_uriResolution 1044 * @return 1045 * Defines the resolution level for URIs when serializing URIs. 1046 */ 1047 protected final UriResolution getUriResolution() { 1048 return uriResolution; 1049 } 1050 1051 /** 1052 * Configuration property: URI relativity. 1053 * 1054 * @see #SERIALIZER_uriRelativity 1055 * @return 1056 * Defines what relative URIs are relative to when serializing any of the following: 1057 */ 1058 protected final UriRelativity getUriRelativity() { 1059 return uriRelativity; 1060 } 1061 1062 /** 1063 * Configuration property: Trim strings. 1064 * 1065 * @see #SERIALIZER_trimStrings 1066 * @return 1067 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being serialized. 1068 */ 1069 protected final boolean isUseWhitespace() { 1070 return useWhitespace; 1071 } 1072 1073 /** 1074 * Configuration property: Serializer listener. 1075 * 1076 * @see #SERIALIZER_listener 1077 * @return 1078 * Class used to listen for errors and warnings that occur during serialization. 1079 */ 1080 protected final Class<? extends SerializerListener> getListener() { 1081 return listener; 1082 } 1083 1084 @Override /* Context */ 1085 public ObjectMap asMap() { 1086 return super.asMap() 1087 .append("Serializer", new ObjectMap() 1088 .append("addBeanTypes", addBeanTypes) 1089 .append("trimNullProperties", trimNullProperties) 1090 .append("trimEmptyCollections", trimEmptyCollections) 1091 .append("trimEmptyMaps", trimEmptyMaps) 1092 .append("trimStrings", trimStrings) 1093 .append("sortCollections", sortCollections) 1094 .append("sortMaps", sortMaps) 1095 .append("addRootType", addRootType) 1096 .append("uriContext", uriContext) 1097 .append("uriResolution", uriResolution) 1098 .append("uriRelativity", uriRelativity) 1099 .append("listener", listener) 1100 ); 1101 } 1102 1103 /** 1104 * @deprecated Use {@link #SERIALIZER_addRootType}. 1105 */ 1106 @Deprecated 1107 public static final String SERIALIZER_abridged = SERIALIZER_addRootType; 1108 1109 /** 1110 * @deprecated Use {@link #SERIALIZER_addBeanTypes}. 1111 */ 1112 @Deprecated 1113 public static final String SERIALIZER_addBeanTypeProperties = SERIALIZER_addBeanTypes; 1114 1115 /** 1116 * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_detectRecursions}. 1117 */ 1118 @Deprecated 1119 public static final String SERIALIZER_detectRecursions = BeanTraverseContext.BEANTRAVERSE_detectRecursions; 1120 1121 /** 1122 * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_ignoreRecursions}. 1123 */ 1124 @Deprecated 1125 public static final String SERIALIZER_ignoreRecursions = BeanTraverseContext.BEANTRAVERSE_ignoreRecursions; 1126 1127 /** 1128 * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_initialDepth}. 1129 */ 1130 @Deprecated 1131 public static final String SERIALIZER_initialDepth = BeanTraverseContext.BEANTRAVERSE_initialDepth; 1132 1133 /** 1134 * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_maxDepth}. 1135 */ 1136 @Deprecated 1137 public static final String SERIALIZER_maxDepth = BeanTraverseContext.BEANTRAVERSE_maxDepth; 1138 1139 /** 1140 * @deprecated Use {@link BeanTraverseContext#BEANTRAVERSE_maxDepth}. 1141 */ 1142 @Deprecated 1143 public static final String SERIALIZER_maxIndent = BeanTraverseContext.BEANTRAVERSE_maxDepth; 1144 1145 /** 1146 * @deprecated Unused. 1147 */ 1148 @Deprecated 1149 public static final String SERIALIZER_quoteChar = PREFIX + "quoteChar.s"; 1150 1151 /** 1152 * @deprecated Use {@link #getMediaTypeRanges()} 1153 */ 1154 @SuppressWarnings("javadoc") 1155 @Deprecated 1156 public final MediaType[] getMediaTypes() { 1157 return null; 1158 } 1159}