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