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