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