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.parser.*; 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(boolean)} 054 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#detectRecursions()} 055 * </ul> 056 * </ul> 057 * 058 * <h5 class='section'>Description:</h5> 059 * <p> 060 * 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 ParseException ParseExceptions} with the message <js>"Depth too deep. Stack overflow occurred."</js>. 066 * 067 * <p> 068 * The behavior when recursions are detected depends on the value for {@link #BEANTRAVERSE_ignoreRecursions}. 069 * 070 * <p> 071 * For example, if a model contains the links A->B->C->A, then the JSON generated will look like 072 * the following when <jsf>BEANTRAVERSE_ignoreRecursions</jsf> is <jk>true</jk>... 073 * 074 * <p class='bcode w800'> 075 * {A:{B:{C:<jk>null</jk>}}} 076 * </p> 077 * 078 * <ul class='notes'> 079 * <li> 080 * Checking for recursion can cause a small performance penalty. 081 * </ul> 082 * 083 * <h5 class='section'>Example:</h5> 084 * <p class='bcode w800'> 085 * <jc>// Create a serializer that never adds _type to nodes.</jc> 086 * WriterSerializer s = JsonSerializer 087 * .<jsm>create</jsm>() 088 * .detectRecursions() 089 * .ignoreRecursions() 090 * .build(); 091 * 092 * <jc>// Same, but use property.</jc> 093 * WriterSerializer s = JsonSerializer 094 * .<jsm>create</jsm>() 095 * .set(<jsf>BEANTRAVERSE_detectRecursions</jsf>, <jk>true</jk>) 096 * .set(<jsf>BEANTRAVERSE_ignoreRecursions</jsf>, <jk>true</jk>) 097 * .build(); 098 * 099 * <jc>// Create a POJO model with a recursive loop.</jc> 100 * <jk>public class</jk> A { 101 * <jk>public</jk> Object <jf>f</jf>; 102 * } 103 * A a = <jk>new</jk> A(); 104 * a.<jf>f</jf> = a; 105 * 106 * <jc>// Produces "{f:null}"</jc> 107 * String json = s.serialize(a); 108 * </p> 109 */ 110 public static final String BEANTRAVERSE_detectRecursions = PREFIX + ".detectRecursions.b"; 111 112 /** 113 * Configuration property: Ignore recursion errors. 114 * 115 * <h5 class='section'>Property:</h5> 116 * <ul class='spaced-list'> 117 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_ignoreRecursions BEANTRAVERSE_ignoreRecursions} 118 * <li><b>Name:</b> <js>"BeanTraverseContext.ignoreRecursions.b"</js> 119 * <li><b>Data type:</b> <jk>boolean</jk> 120 * <li><b>System property:</b> <c>BeanTraverseContext.ignoreRecursions</c> 121 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_IGNORERECURSIONS</c> 122 * <li><b>Default:</b> <jk>false</jk> 123 * <li><b>Session property:</b> <jk>false</jk> 124 * <li><b>Annotations:</b> 125 * <ul> 126 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#ignoreRecursions()} 127 * </ul> 128 * <li><b>Methods:</b> 129 * <ul> 130 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions(boolean)} 131 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#ignoreRecursions()} 132 * </ul> 133 * </ul> 134 * 135 * <h5 class='section'>Description:</h5> 136 * <p> 137 * Used in conjunction with {@link #BEANTRAVERSE_detectRecursions}. 138 * <br>Setting is ignored if <jsf>BEANTRAVERSE_detectRecursions</jsf> is <jk>false</jk>. 139 * 140 * <p> 141 * If <jk>true</jk>, when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 142 * <br>Otherwise, a {@link BeanRecursionException} is thrown with the message <js>"Recursion occurred, stack=..."</js>. 143 */ 144 public static final String BEANTRAVERSE_ignoreRecursions = PREFIX + ".ignoreRecursions.b"; 145 146 /** 147 * Configuration property: Initial depth. 148 * 149 * <h5 class='section'>Property:</h5> 150 * <ul class='spaced-list'> 151 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_initialDepth BEANTRAVERSE_initialDepth} 152 * <li><b>Name:</b> <js>"BeanTraverseContext.initialDepth.i"</js> 153 * <li><b>Data type:</b> <jk>int</jk> 154 * <li><b>System property:</b> <c>BeanTraverseContext.initialDepth</c> 155 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_INITIALDEPTH</c> 156 * <li><b>Default:</b> <c>0</c> 157 * <li><b>Session property:</b> <jk>false</jk> 158 * <li><b>Annotations:</b> 159 * <ul> 160 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#initialDepth()} 161 * </ul> 162 * <li><b>Methods:</b> 163 * <ul> 164 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#initialDepth(int)} 165 * </ul> 166 * </ul> 167 * 168 * <h5 class='section'>Description:</h5> 169 * <p> 170 * The initial indentation level at the root. 171 * <br>Useful when constructing document fragments that need to be indented at a certain level. 172 * 173 * <h5 class='section'>Example:</h5> 174 * <p class='bcode w800'> 175 * <jc>// Create a serializer with whitespace enabled and an initial depth of 2.</jc> 176 * WriterSerializer s = JsonSerializer 177 * .<jsm>create</jsm>() 178 * .ws() 179 * .initialDepth(2) 180 * .build(); 181 * 182 * <jc>// Same, but use property.</jc> 183 * WriterSerializer s = JsonSerializer 184 * .<jsm>create</jsm>() 185 * .set(<jsf>BEANTRAVERSE_useWhitespace</jsf>, <jk>true</jk>) 186 * .set(<jsf>BEANTRAVERSE_initialDepth</jsf>, 2) 187 * .build(); 188 * 189 * <jc>// Produces "\t\t{\n\t\t\t'foo':'bar'\n\t\t}\n"</jc> 190 * String json = s.serialize(<jk>new</jk> MyBean()); 191 * </p> 192 */ 193 public static final String BEANTRAVERSE_initialDepth = PREFIX + ".initialDepth.i"; 194 195 /** 196 * Configuration property: Max traversal depth. 197 * 198 * <h5 class='section'>Property:</h5> 199 * <ul class='spaced-list'> 200 * <li><b>ID:</b> {@link org.apache.juneau.BeanTraverseContext#BEANTRAVERSE_maxDepth BEANTRAVERSE_maxDepth} 201 * <li><b>Name:</b> <js>"BeanTraverseContext.maxDepth.i"</js> 202 * <li><b>Data type:</b> <jk>int</jk> 203 * <li><b>System property:</b> <c>BeanTraverseContext.maxDepth</c> 204 * <li><b>Environment variable:</b> <c>BEANTRAVERSECONTEXT_MAXDEPTH</c> 205 * <li><b>Default:</b> <c>100</c> 206 * <li><b>Session property:</b> <jk>false</jk> 207 * <li><b>Annotations:</b> 208 * <ul> 209 * <li class='ja'>{@link org.apache.juneau.annotation.BeanConfig#maxDepth()} 210 * </ul> 211 * <li><b>Methods:</b> 212 * <ul> 213 * <li class='jm'>{@link org.apache.juneau.BeanTraverseBuilder#maxDepth(int)} 214 * </ul> 215 * </ul> 216 * 217 * <h5 class='section'>Description:</h5> 218 * <p> 219 * Abort traversal if specified depth is reached in the POJO tree. 220 * <br>If this depth is exceeded, an exception is thrown. 221 * 222 * <h5 class='section'>Example:</h5> 223 * <p class='bcode w800'> 224 * <jc>// Create a serializer that throws an exception if the depth is greater than 20.</jc> 225 * WriterSerializer s = JsonSerializer 226 * .<jsm>create</jsm>() 227 * .maxDepth(20) 228 * .build(); 229 * 230 * <jc>// Same, but use property.</jc> 231 * WriterSerializer s = JsonSerializer 232 * .<jsm>create</jsm>() 233 * .set(<jsf>BEANTRAVERSE_maxDepth</jsf>, 20) 234 * .build(); 235 * </p> 236 */ 237 public static final String BEANTRAVERSE_maxDepth = PREFIX + ".maxDepth.i"; 238 239 //------------------------------------------------------------------------------------------------------------------- 240 // Instance 241 //------------------------------------------------------------------------------------------------------------------- 242 243 private final int initialDepth, maxDepth; 244 private final boolean 245 detectRecursions, 246 ignoreRecursions; 247 248 /** 249 * Constructor 250 * 251 * @param ps 252 * The property store containing all the settings for this object. 253 */ 254 protected BeanTraverseContext(PropertyStore ps) { 255 super(ps); 256 257 maxDepth = getIntegerProperty(BEANTRAVERSE_maxDepth, 100); 258 initialDepth = getIntegerProperty(BEANTRAVERSE_initialDepth, 0); 259 detectRecursions = getBooleanProperty(BEANTRAVERSE_detectRecursions, false); 260 ignoreRecursions = getBooleanProperty(BEANTRAVERSE_ignoreRecursions, false); 261 } 262 263 @Override /* Context */ 264 public BeanTraverseBuilder builder() { 265 return null; 266 } 267 268 @Override /* Context */ 269 public BeanTraverseSession createSession() { 270 return new BeanTraverseSession(this, createDefaultSessionArgs()); 271 } 272 273 @Override /* Context */ 274 public BeanTraverseSession createSession(BeanSessionArgs args) { 275 return new BeanTraverseSession(this, args); 276 } 277 278 279 //----------------------------------------------------------------------------------------------------------------- 280 // Properties 281 //----------------------------------------------------------------------------------------------------------------- 282 283 /** 284 * Configuration property: Automatically detect POJO recursions. 285 * 286 * @see #BEANTRAVERSE_detectRecursions 287 * @return 288 * <jk>true</jk> if recursions should be checked for during traversal. 289 */ 290 protected final boolean isDetectRecursions() { 291 return detectRecursions; 292 } 293 294 /** 295 * Configuration property: Ignore recursion errors. 296 * 297 * @see #BEANTRAVERSE_ignoreRecursions 298 * @return 299 * <jk>true</jk> if when we encounter the same object when traversing a tree, we set the value to <jk>null</jk>. 300 * <br>Otherwise, an exception is thrown with the message <js>"Recursion occurred, stack=..."</js>. 301 */ 302 protected final boolean isIgnoreRecursions() { 303 return ignoreRecursions; 304 } 305 306 /** 307 * Configuration property: Initial depth. 308 * 309 * @see #BEANTRAVERSE_initialDepth 310 * @return 311 * The initial indentation level at the root. 312 */ 313 protected final int getInitialDepth() { 314 return initialDepth; 315 } 316 317 /** 318 * Configuration property: Max traversal depth. 319 * 320 * @see #BEANTRAVERSE_maxDepth 321 * @return 322 * The depth at which traversal is aborted if depth is reached in the POJO tree. 323 * <br>If this depth is exceeded, an exception is thrown. 324 */ 325 protected final int getMaxDepth() { 326 return maxDepth; 327 } 328 329 //----------------------------------------------------------------------------------------------------------------- 330 // Other methods 331 //----------------------------------------------------------------------------------------------------------------- 332 333 @Override /* Context */ 334 public ObjectMap toMap() { 335 return super.toMap() 336 .append("BeanTraverseContext", new DefaultFilteringObjectMap() 337 .append("detectRecursions", detectRecursions) 338 .append("maxDepth", maxDepth) 339 .append("ignoreRecursions", ignoreRecursions) 340 .append("initialDepth", initialDepth) 341 ); 342 } 343}