001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall; 018 019import static org.apache.juneau.commons.utils.SystemUtils.*; 020 021import org.apache.juneau.commons.collections.*; 022 023/** 024 * Parent class for all classes that traverse POJOs. 025 * 026 * <h5 class='topic'>Description</h5> 027 * <p> 028 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs. 029 * 030 * <h5 class='section'>Notes:</h5><ul> 031 * <li class='note'>This class is thread safe and reusable. 032 * </ul> 033 * 034 */ 035@SuppressWarnings({ 036 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 037 "rawtypes" // The protected constructor takes the raw Builder type, shared by subclasses that have different self-type parameters 038}) 039public abstract class MarshallingTraverseContext extends MarshallingContextable { 040 041 /** 042 * Builder class. 043 */ 044 @SuppressWarnings({ 045 "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name. 046 }) 047 public abstract static class Builder<SELF extends Builder<SELF>> extends MarshallingContextable.Builder<SELF> { 048 049 private boolean detectRecursions; 050 private boolean ignoreRecursions; 051 private int initialDepth; 052 private int maxDepth; 053 054 /** 055 * Constructor, default settings. 056 */ 057 protected Builder() { 058 detectRecursions = env("MarshallingTraverseContext.detectRecursions", false); 059 ignoreRecursions = env("MarshallingTraverseContext.ignoreRecursions", false); 060 initialDepth = env("MarshallingTraverseContext.initialDepth", 0); 061 maxDepth = env("MarshallingTraverseContext.maxDepth", 100); 062 } 063 064 /** 065 * Copy constructor. 066 * 067 * @param copyFrom The bean to copy from. Cannot be <jk>null</jk>. 068 */ 069 protected Builder(MarshallingTraverseContext copyFrom) { 070 super(copyFrom); 071 detectRecursions = copyFrom.getDetectRecursions(); 072 ignoreRecursions = copyFrom.getIgnoreRecursions(); 073 initialDepth = copyFrom.getInitialDepth(); 074 maxDepth = copyFrom.getMaxDepth(); 075 } 076 077 /** 078 * Copy constructor. 079 * 080 * @param copyFrom The builder to copy from. Cannot be <jk>null</jk>. 081 */ 082 protected Builder(Builder<?> copyFrom) { 083 super(copyFrom); 084 detectRecursions = copyFrom.detectRecursions; 085 ignoreRecursions = copyFrom.ignoreRecursions; 086 initialDepth = copyFrom.initialDepth; 087 maxDepth = copyFrom.maxDepth; 088 } 089 090 /** 091 * Automatically detect POJO recursions. 092 * 093 * <p> 094 * When enabled, specifies that recursions should be checked for during traversal. 095 * 096 * <p> 097 * Recursions can occur when traversing models that aren't true trees but rather contain loops. 098 * <br>When recursion detection is disabled, such loops are instead bounded by {@link #maxDepth(int) maxDepth} 099 * (default <c>100</c>): the over-depth branch is silently truncated, producing finite but semantically-incomplete output. 100 * <br>If the stack is exhausted before <c>maxDepth</c> is reached, the resulting {@link StackOverflowError} is 101 * converted to a {@link MarshallingRecursionException MarshallingRecursionException} with the message <js>"Depth too deep. Stack overflow occurred."</js>. 102 * 103 * <h5 class='section'>Notes:</h5><ul> 104 * <li class='note'> 105 * Checking for recursion can cause a small performance penalty. 106 * <li class='note'> 107 * This is the recommended way to fail-fast on cyclic bean graphs such as parent/child 108 * {@link ParentProperty @ParentProperty} references. Under the default config such cycles are instead 109 * silently truncated at {@link #maxDepth(int) maxDepth} (see the {@link ParentProperty} Javadoc). 110 * </ul> 111 * 112 * <h5 class='section'>Example:</h5> 113 * <p class='bjava'> 114 * <jc>// Create a serializer that automatically checks for recursions.</jc> 115 * WriterSerializer <jv>serializer</jv> = JsonSerializer 116 * .<jsm>create</jsm>() 117 * .detectRecursions() 118 * .build(); 119 * 120 * <jc>// Create a POJO model with a recursive loop.</jc> 121 * <jk>public class</jk> MyBean { 122 * <jk>public</jk> Object <jf>f</jf>; 123 * } 124 * MyBean <jv>bean</jv> = <jk>new</jk> MyBean(); 125 * <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>; 126 * 127 * <jc>// Throws a SerializeException and not a StackOverflowError</jc> 128 * String <jv>json</jv> = <jv>serializer</jv>.write(<jv>bean</jv>); 129 * </p> 130 * 131 * @return This object. 132 */ 133 public SELF detectRecursions() { 134 return detectRecursions(true); 135 } 136 137 /** 138 * Same as {@link #detectRecursions()} but allows you to explicitly specify the value. 139 * 140 * @param value The value for this setting. 141 * @return This object. 142 */ 143 public SELF detectRecursions(boolean value) { 144 detectRecursions = value; 145 return self(); 146 } 147 148 @Override /* Overridden from Context.Builder<?> */ 149 public HashKey hashKey() { 150 // @formatter:off 151 return HashKey.of( 152 super.hashKey(), 153 detectRecursions, 154 ignoreRecursions, 155 initialDepth, 156 maxDepth 157 ); 158 // @formatter:on 159 } 160 161 /** 162 * Ignore recursion errors. 163 * 164 * <p> 165 * When enabled, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 166 * 167 * <p> 168 * For example, if a model contains the links A->B->C->A, then the JSON generated will look like 169 * the following when this setting is <jk>true</jk>... 170 * 171 * <p class='bjson'> 172 * {A:{B:{C:<jk>null</jk>}}} 173 * </p> 174 * 175 * <h5 class='section'>Notes:</h5><ul> 176 * <li class='note'> 177 * Checking for recursion can cause a small performance penalty. 178 * <li class='note'> 179 * Use this to serialize cyclic bean graphs (such as parent/child {@link ParentProperty @ParentProperty} 180 * references) by emitting the repeated node as <jk>null</jk> so the output round-trips cleanly. 181 * </ul> 182 * 183 * <h5 class='section'>Example:</h5> 184 * <p class='bjava'> 185 * <jc>// Create a serializer ignores recursions.</jc> 186 * WriterSerializer <jv>serializer</jv> = JsonSerializer 187 * .<jsm>create</jsm>() 188 * .ignoreRecursions() 189 * .build(); 190 * 191 * <jc>// Create a POJO model with a recursive loop.</jc> 192 * <jk>public class</jk> MyBean { 193 * <jk>public</jk> Object <jf>f</jf>; 194 * } 195 * MyBean <jv>bean</jv> = <jk>new</jk> MyBean(); 196 * <jv>bean</jv>.<jf>f</jf> = <jv>bean</jv>; 197 * 198 * <jc>// Produces "{f:null}"</jc> 199 * String <jv>json</jv> = <jv>serializer</jv>.write(<jv>bean</jv>); 200 * </p> 201 * 202 * @return This object. 203 */ 204 public SELF ignoreRecursions() { 205 return ignoreRecursions(true); 206 } 207 208 /** 209 * Same as {@link #ignoreRecursions()} but allows you to explicitly specify the value. 210 * 211 * @param value The value for this setting. 212 * @return This object. 213 */ 214 public SELF ignoreRecursions(boolean value) { 215 ignoreRecursions = value; 216 return self(); 217 } 218 219 /** 220 * Initial depth. 221 * 222 * <p> 223 * The initial indentation level at the root. 224 * 225 * <p> 226 * Useful when constructing document fragments that need to be indented at a certain level when whitespace is enabled. 227 * 228 * <h5 class='section'>Example:</h5> 229 * <p class='bjava'> 230 * <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc> 231 * WriterSerializer <jv>serializer</jv> = JsonSerializer 232 * .<jsm>create</jsm>() 233 * .ws() 234 * .initialDepth(2) 235 * .build(); 236 * 237 * <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc> 238 * String <jv>json</jv> = <jv>serializer</jv>.write(<jk>new</jk> MyBean()); 239 * </p> 240 * 241 * @param value 242 * The new value for this setting. 243 * <br>The default is <c>0</c>. 244 * @return This object. 245 */ 246 public SELF initialDepth(int value) { 247 initialDepth = value; 248 return self(); 249 } 250 251 /** 252 * Max traversal depth. 253 * 254 * <p> 255 * Specifies the maximum depth traversed in the POJO tree. 256 * 257 * <p> 258 * When this depth is exceeded, the over-depth nodes are silently dropped (truncated) from the output — no 259 * exception is thrown. This is a size guard that bounds the output of deeply-nested (or cyclic) models; it is 260 * <b>not</b> a cycle detector. For genuine recursive references, enable {@link #detectRecursions()} to fail-fast 261 * with a {@link MarshallingRecursionException}, or {@link #ignoreRecursions()} to emit repeated nodes as 262 * <jk>null</jk> so the output round-trips cleanly. 263 * 264 * <h5 class='section'>Example:</h5> 265 * <p class='bjava'> 266 * <jc>// Create a serializer that truncates output beyond a depth of 20.</jc> 267 * WriterSerializer <jv>serializer</jv> = JsonSerializer 268 * .<jsm>create</jsm>() 269 * .maxDepth(20) 270 * .build(); 271 * </p> 272 * 273 * <h5 class='section'>See Also:</h5><ul> 274 * <li class='jm'>{@link Builder#maxDepth(int)} 275 * </ul> 276 * 277 * @param value 278 * The new value for this setting. 279 * <br>The default is <c>100</c>. 280 * @return This object. 281 */ 282 public SELF maxDepth(int value) { 283 maxDepth = value; 284 return self(); 285 } 286 287 288 } 289 290 private final boolean detectRecursions; 291 private final boolean ignoreRecursions; 292 private final int initialDepth; 293 private final int maxDepth; 294 private final boolean actualDetectRecursions; 295 296 /** 297 * Constructor 298 * 299 * @param builder The builder for this object. Cannot be <jk>null</jk>. 300 */ 301 protected MarshallingTraverseContext(Builder builder) { 302 super(builder); 303 304 detectRecursions = builder.detectRecursions; 305 ignoreRecursions = builder.ignoreRecursions; 306 initialDepth = builder.initialDepth; 307 maxDepth = builder.maxDepth; 308 309 actualDetectRecursions = detectRecursions || ignoreRecursions || super.isDebug(); 310 } 311 312 @Override /* Overridden from Context */ 313 public abstract Builder<?> copy(); 314 315 /** 316 * Initial depth. 317 * 318 * @see Builder#initialDepth(int) 319 * @return 320 * The initial indentation level at the root. 321 */ 322 public final int getInitialDepth() { return initialDepth; } 323 324 /** 325 * Max traversal depth. 326 * 327 * @see Builder#maxDepth(int) 328 * @return 329 * The depth beyond which nodes in the POJO tree are dropped (truncated) from the output. 330 * <br>Values deeper than this limit are silently omitted rather than causing an exception to be thrown. 331 * <br>This is a size guard, not a cycle detector. 332 */ 333 public final int getMaxDepth() { return maxDepth; } 334 335 /** 336 * Automatically detect POJO recursions. 337 * 338 * @see Builder#detectRecursions() 339 * @return 340 * <jk>true</jk> if recursions should be checked for during traversal. 341 */ 342 public final boolean isDetectRecursions() { return detectRecursions; } 343 344 /** 345 * Whether recursion detection is effectively enabled during traversal. 346 * This is {@link #isDetectRecursions()} OR {@link #isIgnoreRecursions()} OR {@link #isDebug()}. 347 */ 348 protected final boolean shouldDetectRecursions() { return actualDetectRecursions; } 349 350 /** 351 * Ignore recursion errors. 352 * 353 * @see Builder#ignoreRecursions() 354 * @return 355 * <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 356 * <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>. 357 */ 358 public final boolean isIgnoreRecursions() { return ignoreRecursions; } 359 360 /** 361 * Detect recursions flag (raw value). 362 * 363 * @return The detect recursions flag. 364 */ 365 protected final boolean getDetectRecursions() { return detectRecursions; } 366 367 /** 368 * Ignore recursions flag (raw value). 369 * 370 * @return The ignore recursions flag. 371 */ 372 protected final boolean getIgnoreRecursions() { return ignoreRecursions; } 373 374 @Override /* Overridden from MarshallingContextable */ 375 protected FluentMap<String,Object> properties() { 376 return super.properties() 377 .a("detectRecursions", detectRecursions) 378 .a("ignoreRecursions", ignoreRecursions) 379 .a("initialDepth", initialDepth) 380 .a("maxDepth", maxDepth); 381 } 382}