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; 014 015 016import org.apache.juneau.annotation.*; 017import org.apache.juneau.collections.*; 018 019/** 020 * Parent class for all classes that traverse POJOs. 021 * 022 * <h5 class='topic'>Description</h5> 023 * 024 * Base class that serves as the parent class for all serializers and other classes that traverse POJOs. 025 */ 026@ConfigurableContext 027public abstract class BeanTraverseContext extends BeanContext { 028 029 //------------------------------------------------------------------------------------------------------------------- 030 // Configurable properties 031 //------------------------------------------------------------------------------------------------------------------- 032 033 static final String PREFIX = "BeanTraverseContext"; 034 035 /** 036 * Configuration property: Automatically detect POJO recursions. 037 * 038 * <h5 class='section'>Property:</h5> 039 * <ul class='spaced-list'> 040 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_detectRecursions BEANTRAVERSE_detectRecursions} 041 * <li><b>Name:</b> <js>"BeanTraverseContext.detectRecursions.b"</js> 042 * <li><b>Data type:</b> <jk>boolean</jk> 043 * <li><b>System property:</b> <c>BeanTraverseContext.detectRecursions</c> 044 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_DETECTRECURSIONS</c> 045 * <li><b>Default:</b> <jk>false</jk> 046 * <li><b>Session property:</b> <jk>false</jk> 047 * <li><b>Annotations:</b> 048 * <ul> 049 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#detectRecursions()} 050 * </ul> 051 * <li><b>Methods:</b> 052 * <ul> 053 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions()} 054 * </ul> 055 * </ul> 056 * 057 * <h5 class='section'>Description:</h5> 058 * 059 * <p> 060 * When enabled, specifies that recursions should be checked for during traversal. 061 * 062 * <p> 063 * Recursions can occur when traversing models that aren't true trees but rather contain loops. 064 * <br>In general, unchecked recursions cause stack-overflow-errors. 065 * <br>These show up as {@link BeanRecursionException BeanRecursionException} with the message <js>"Depth too deep. Stack overflow occurred."</js>. 066 * 067 * <ul class='notes'> 068 * <li> 069 * Checking for recursion can cause a small performance penalty. 070 * </ul> 071 * 072 * <h5 class='section'>Example:</h5> 073 * <p class='bcode w800'> 074 * <jc>// Create a serializer that automatically checks for recursions.</jc> 075 * WriterSerializer s = JsonSerializer 076 * .<jsm>create</jsm>() 077 * .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>) 078 * .build(); 079 * 080 * <jc>// Create a POJO model with a recursive loop.</jc> 081 * <jk>public class</jk> A { 082 * <jk>public</jk> Object <jf>f</jf>; 083 * } 084 * A a = <jk>new</jk> A(); 085 * a.<jf>f</jf> = a; 086 * 087 * <jc>// Throws a SerializeException</jc> 088 * String json = s.serialize(a); 089 * </p> 090 */ 091 public static final String BEANTRAVERSE_detectRecursions = PREFIX + ".detectRecursions.b"; 092 093 /** 094 * Configuration property: Ignore recursion errors. 095 * 096 * <h5 class='section'>Property:</h5> 097 * <ul class='spaced-list'> 098 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_ignoreRecursions BEANTRAVERSE_ignoreRecursions} 099 * <li><b>Name:</b> <js>"BeanTraverseContext.ignoreRecursions.b"</js> 100 * <li><b>Data type:</b> <jk>boolean</jk> 101 * <li><b>System property:</b> <c>BeanTraverseContext.ignoreRecursions</c> 102 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_IGNORERECURSIONS</c> 103 * <li><b>Default:</b> <jk>false</jk> 104 * <li><b>Session property:</b> <jk>false</jk> 105 * <li><b>Annotations:</b> 106 * <ul> 107 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#ignoreRecursions()} 108 * </ul> 109 * <li><b>Methods:</b> 110 * <ul> 111 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions()} 112 * </ul> 113 * </ul> 114 * 115 * <h5 class='section'>Description:</h5> 116 * 117 * <p> 118 * When enabled, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 119 * 120 * <p> 121 * For example, if a model contains the links A->B->C->A, then the JSON generated will look like 122 * the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>... 123 * 124 * <ul class='notes'> 125 * <li> 126 * Checking for recursion can cause a small performance penalty. 127 * </ul> 128 * 129 * <p class='bcode w800'> 130 * {A:{B:{C:<jk>null</jk>}}} 131 * </p> 132 * 133 * <h5 class='section'>Example:</h5> 134 * <p class='bcode w800'> 135 * <jc>// Create a serializer ignores recursions.</jc> 136 * WriterSerializer s = JsonSerializer 137 * .<jsm>create</jsm>() 138 * .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>) 139 * .build(); 140 * 141 * <jc>// Create a POJO model with a recursive loop.</jc> 142 * <jk>public class</jk> A { 143 * <jk>public</jk> Object <jf>f</jf>; 144 * } 145 * A a = <jk>new</jk> A(); 146 * a.<jf>f</jf> = a; 147 * 148 * <jc>// Produces "{f:null}"</jc> 149 * String json = s.serialize(a); 150 * </p> 151 */ 152 public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + ".ignoreRecursions.b"; 153 154 /** 155 * Configuration property: Initial depth. 156 * 157 * <h5 class='section'>Property:</h5> 158 * <ul class='spaced-list'> 159 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_initialDepth BEANTRAVERSE_initialDepth} 160 * <li><b>Name:</b> <js>"BeanTraverseContext.initialDepth.i"</js> 161 * <li><b>Data type:</b> <jk>int</jk> 162 * <li><b>System property:</b> <c>BeanTraverseContext.initialDepth</c> 163 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_INITIALDEPTH</c> 164 * <li><b>Default:</b> <c>0</c> 165 * <li><b>Session property:</b> <jk>false</jk> 166 * <li><b>Annotations:</b> 167 * <ul> 168 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#initialDepth()} 169 * </ul> 170 * <li><b>Methods:</b> 171 * <ul> 172 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#initialDepth(int)} 173 * </ul> 174 * </ul> 175 * 176 * <h5 class='section'>Description:</h5> 177 * 178 * <p> 179 * The initial indentation level at the root. 180 * 181 * <p> 182 * Useful when constructing document fragments that need to be indented at a certain level when whitespace is enabled. 183 * 184 * <h5 class='section'>Example:</h5> 185 * <p class='bcode w800'> 186 * <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc> 187 * WriterSerializer s = JsonSerializer 188 * .<jsm>create</jsm>() 189 * .ws() 190 * .initialDepth(2) 191 * .build(); 192 * 193 * <jc>// Same, but use property.</jc> 194 * WriterSerializer s = JsonSerializer 195 * .<jsm>create</jsm>() 196 * .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>) 197 * .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2) 198 * .build(); 199 * 200 * <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc> 201 * String json = s.serialize(<jk>new</jk> MyBean()); 202 * </p> 203 */ 204 public static final String BEANTRAVERSE_initialDepth = PREFIX + ".initialDepth.i"; 205 206 /** 207 * Configuration property: Max traversal depth. 208 * 209 * <h5 class='section'>Property:</h5> 210 * <ul class='spaced-list'> 211 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_maxDepth BEANTRAVERSE_maxDepth} 212 * <li><b>Name:</b> <js>"BeanTraverseContext.maxDepth.i"</js> 213 * <li><b>Data type:</b> <jk>int</jk> 214 * <li><b>System property:</b> <c>BeanTraverseContext.maxDepth</c> 215 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_MAXDEPTH</c> 216 * <li><b>Default:</b> <c>100</c> 217 * <li><b>Session property:</b> <jk>false</jk> 218 * <li><b>Annotations:</b> 219 * <ul> 220 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#maxDepth()} 221 * </ul> 222 * <li><b>Methods:</b> 223 * <ul> 224 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#maxDepth(int)} 225 * </ul> 226 * </ul> 227 * 228 * <h5 class='section'>Description:</h5> 229 * 230 * <p> 231 * When enabled, abort traversal if specified depth is reached in the POJO tree. 232 * 233 * <p> 234 * If this depth is exceeded, an exception is thrown. 235 * 236 * <p> 237 * This prevents stack overflows from occurring when trying to traverse models with recursive references. 238 * 239 * <h5 class='section'>Example:</h5> 240 * <p class='bcode w800'> 241 * <jc>// Create a serializer that throws an exception if the depth reaches greater than 20.</jc> 242 * WriterSerializer s = JsonSerializer 243 * .<jsm>create</jsm>() 244 * .maxDepth(20) 245 * .build(); 246 * 247 * <jc>// Same, but use property.</jc> 248 * WriterSerializer s = JsonSerializer 249 * .<jsm>create</jsm>() 250 * .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20) 251 * .build(); 252 * </p> 253 */ 254 public static final String BEANTRAVERSE_maxDepth = PREFIX + ".maxDepth.i"; 255 256 //------------------------------------------------------------------------------------------------------------------- 257 // Instance 258 //------------------------------------------------------------------------------------------------------------------- 259 260 private final int initialDepth, maxDepth; 261 private final boolean 262 detectRecursions, 263 ignoreRecursions; 264 265 /** 266 * Constructor 267 * 268 * @param ps 269 * The property store containing all the settings for this object. 270 */ 271 protected BeanTraverseContext(PropertyStore ps) { 272 super(ps); 273 274 maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100); 275 initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0); 276 ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false); 277 detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, ignoreRecursions); 278 } 279 280 @Override /* Context */ 281 public BeanTraverseBuilder builder() { 282 return null; 283 } 284 285 @Override /* Context */ 286 public BeanTraverseSession createSession() { 287 return new BeanTraverseSession(this, createDefaultSessionArgs()); 288 } 289 290 @Override /* Context */ 291 public BeanTraverseSession createSession(BeanSessionArgs args) { 292 return new BeanTraverseSession(this, args); 293 } 294 295 296 //----------------------------------------------------------------------------------------------------------------- 297 // Properties 298 //----------------------------------------------------------------------------------------------------------------- 299 300 /** 301 * Automatically detect POJO recursions. 302 * 303 * @see #BEANTRAVERSE_detectRecursions 304 * @return 305 * <jk>true</jk> if recursions should be checked for during traversal. 306 */ 307 protected final boolean isDetectRecursions() { 308 return detectRecursions; 309 } 310 311 /** 312 * Ignore recursion errors. 313 * 314 * @see #BEANTRAVERSE_ignoreRecursions 315 * @return 316 * <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 317 * <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>. 318 */ 319 protected final boolean isIgnoreRecursions() { 320 return ignoreRecursions; 321 } 322 323 /** 324 * Initial depth. 325 * 326 * @see #BEANTRAVERSE_initialDepth 327 * @return 328 * The initial indentation level at the root. 329 */ 330 protected final int getInitialDepth() { 331 return initialDepth; 332 } 333 334 /** 335 * Max traversal depth. 336 * 337 * @see #BEANTRAVERSE_maxDepth 338 * @return 339 * The depth at which traversal is aborted if depth is reached in the POJO tree. 340 * <br>If this depth is exceeded, an exception is thrown. 341 */ 342 protected final int getMaxDepth() { 343 return maxDepth; 344 } 345 346 //----------------------------------------------------------------------------------------------------------------- 347 // Other methods 348 //----------------------------------------------------------------------------------------------------------------- 349 350 @Override /* Context */ 351 public OMap toMap() { 352 return super.toMap() 353 .a("BeanTraverseContext", new DefaultFilteringOMap() 354 .a("detectRecursions", detectRecursions) 355 .a("maxDepth", maxDepth) 356 .a("ignoreRecursions", ignoreRecursions) 357 .a("initialDepth", initialDepth) 358 ); 359 } 360}