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 */ 017 package org.apache.commons.betwixt.expression; 018 019 import java.util.HashMap; 020 import java.util.Map; 021 022 import org.apache.commons.betwixt.BindingConfiguration; 023 import org.apache.commons.betwixt.Options; 024 import org.apache.commons.betwixt.strategy.IdStoringStrategy; 025 import org.apache.commons.betwixt.strategy.ObjectStringConverter; 026 import org.apache.commons.betwixt.strategy.ValueSuppressionStrategy; 027 import org.apache.commons.collections.ArrayStack; 028 import org.apache.commons.logging.Log; 029 import org.apache.commons.logging.LogFactory; 030 031 /** <p><code>Context</code> describes the context used to evaluate 032 * bean expressions. 033 * This is mostly a bean together with a number of context variables. 034 * Context variables are named objects. 035 * In other words, 036 * a context variable associates an object with a string.</p> 037 * 038 * <p> Logging during expression evaluation is done through the logging 039 * instance held by this class. 040 * The object initiating the evaluation should control this logging 041 * and so passing a <code>Log</code> instance is enforced by the constructors.</p> 042 * 043 * <p><code>Context</code> is a natural place to include shared evaluation code. 044 * One of the problems that you get with object graphs is that they can be cyclic. 045 * Xml cannot (directly) include cycles. 046 * Therefore <code>betwixt</code> needs to find and deal properly with cycles. 047 * The algorithm used is to check the parentage of a new child. 048 * If the child is a parent then that operation fails. </p> 049 * 050 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 051 */ 052 public class Context { 053 054 /** Evaluate this bean */ 055 private Object bean; 056 /** Variables map */ 057 private Map variables; 058 /** Store options */ 059 private ArrayStack optionStack = new ArrayStack(); 060 /** 061 * Logging uses commons-logging <code>Log</code> 062 * named <code>org.apache.commons.betwixt</code> 063 */ 064 private Log log; 065 /** Configuration for dynamic binding properties */ 066 private BindingConfiguration bindingConfiguration; 067 068 /** 069 * Construct context with default log 070 */ 071 public Context() { 072 this( null, LogFactory.getLog( Context.class ) ); 073 } 074 075 /** Convenience constructor sets evaluted bean and log. 076 * 077 * @param bean evaluate expressions against this bean 078 * @param log log to this logger 079 * @deprecated 0.5 use constructor which takes a BindingConfiguration 080 */ 081 public Context(Object bean, Log log) { 082 this( bean, log, new BindingConfiguration() ); 083 } 084 085 086 /** Convenience constructor sets evaluted bean and log. 087 * 088 * @param bean evaluate expressions against this bean 089 * @param log log to this logger 090 * @param bindingConfiguration not null 091 */ 092 public Context(Object bean, Log log, BindingConfiguration bindingConfiguration) { 093 this( bean, new HashMap(), log, bindingConfiguration ); 094 } 095 096 /** 097 * Construct a cloned context. 098 * The constructed context should share bean, variables, log and binding configuration. 099 * @param context duplicate the attributes of this bean 100 */ 101 public Context( Context context ) { 102 this(context.bean, context.variables, context.log, context.bindingConfiguration); 103 } 104 105 106 /** Convenience constructor sets evaluted bean, context variables and log. 107 * 108 * @param bean evaluate expressions against this bean 109 * @param variables context variables 110 * @param log log to this logger 111 * @deprecated 0.5 use constructor which takes a converter 112 */ 113 public Context(Object bean, Map variables, Log log) { 114 this( bean, variables, log, new BindingConfiguration() ); 115 } 116 117 /** Convenience constructor sets evaluted bean, context variables and log. 118 * 119 * @param bean evaluate expressions against this bean 120 * @param variables context variables 121 * @param log log to this logger 122 * @param bindingConfiguration not null 123 */ 124 public Context(Object bean, Map variables, Log log, BindingConfiguration bindingConfiguration) { 125 this.bean = bean; 126 this.variables = variables; 127 this.log = log; 128 this.bindingConfiguration = bindingConfiguration; 129 } 130 131 /** Returns a new child context with the given bean but the same log and variables. 132 * 133 * @param newBean create a child context for this bean 134 * @return new Context with new bean but shared variables 135 */ 136 // TODO: need to think about whether this is a good idea and how subclasses 137 // should handle this 138 public Context newContext(Object newBean) { 139 Context context = new Context(this); 140 context.setBean( newBean ); 141 return context; 142 } 143 144 /** 145 * Gets the current bean. 146 * @return the bean against which expressions are evaluated 147 */ 148 public Object getBean() { 149 return bean; 150 } 151 152 /** 153 * Set the current bean. 154 * @param bean the Object against which expressions will be evaluated 155 */ 156 public void setBean(Object bean) { 157 this.bean = bean; 158 } 159 160 /** 161 * Gets context variables. 162 * @return map containing variable values keyed by variable name 163 */ 164 public Map getVariables() { 165 return variables; 166 } 167 168 /** 169 * Sets context variables. 170 * @param variables map containing variable values indexed by varibable name Strings 171 */ 172 public void setVariables(Map variables) { 173 this.variables = variables; 174 } 175 176 /** 177 * Gets the value of a particular context variable. 178 * @param name the name of the variable whose value is to be returned 179 * @return the variable value or null if the variable isn't set 180 */ 181 public Object getVariable(String name) { 182 return variables.get( name ); 183 } 184 185 /** 186 * Sets the value of a particular context variable. 187 * @param name the name of the variable 188 * @param value the value of the variable 189 */ 190 public void setVariable(String name, Object value) { 191 variables.put( name, value ); 192 } 193 194 /** 195 * Gets the current log. 196 * 197 * @return the implementation to which this class logs 198 */ 199 public Log getLog() { 200 return log; 201 } 202 203 /** 204 * Set the log implementation to which this class logs 205 * 206 * @param log the implemetation that this class should log to 207 */ 208 public void setLog(Log log) { 209 this.log = log; 210 } 211 212 /** 213 * Gets object <-> string converter. 214 * @return the Converter to be used for conversions, not null 215 * @since 0.5 216 */ 217 public ObjectStringConverter getObjectStringConverter() { 218 return bindingConfiguration.getObjectStringConverter(); 219 } 220 221 /** 222 * Should <code>ID</code>'s and <code>IDREF</code> attributes 223 * be used to cross-reference matching objects? 224 * 225 * @return true if <code>ID</code> and <code>IDREF</code> 226 * attributes should be used to cross-reference instances 227 * @since 0.5 228 */ 229 public boolean getMapIDs() { 230 return bindingConfiguration.getMapIDs(); 231 } 232 233 /** 234 * The name of the attribute which can be specified in the XML to override the 235 * type of a bean used at a certain point in the schema. 236 * 237 * <p>The default value is 'className'.</p> 238 * 239 * @return The name of the attribute used to overload the class name of a bean 240 * @since 0.5 241 */ 242 public String getClassNameAttribute() { 243 return bindingConfiguration.getClassNameAttribute(); 244 } 245 246 /** 247 * Sets the name of the attribute which can be specified in 248 * the XML to override the type of a bean used at a certain 249 * point in the schema. 250 * 251 * <p>The default value is 'className'.</p> 252 * 253 * @param classNameAttribute The name of the attribute used to overload the class name of a bean 254 * @since 0.5 255 */ 256 public void setClassNameAttribute(String classNameAttribute) { 257 bindingConfiguration.setClassNameAttribute( classNameAttribute ); 258 } 259 260 /** 261 * Gets the <code>ValueSuppressionStrategy</code>. 262 * This is used to control the expression of attributes with certain values. 263 * @since 0.7 264 * @return <code>ValueSuppressionStrategy</code>, not null 265 */ 266 public ValueSuppressionStrategy getValueSuppressionStrategy() { 267 return bindingConfiguration.getValueSuppressionStrategy(); 268 } 269 270 /** 271 * Sets the <code>ValueSuppressionStrategy</code>. 272 * This is used to control the expression of attributes with certain values. 273 * @since 0.7 274 * @param valueSuppressionStrategy <code>ValueSuppressionStrategy</code>, not null 275 */ 276 public void setValueSuppressionStrategy( 277 ValueSuppressionStrategy valueSuppressionStrategy) { 278 bindingConfiguration.setValueSuppressionStrategy(valueSuppressionStrategy); 279 } 280 281 /** 282 * Gets the strategy used to manage storage and retrieval of id's. 283 * @since 0.7 284 * @return Returns the idStoringStrategy, not null 285 */ 286 public IdStoringStrategy getIdMappingStrategy() { 287 return bindingConfiguration.getIdMappingStrategy(); 288 } 289 290 /** 291 * Gets the current <code>Options</code>. 292 * @return <code>Options</code> that currently apply 293 * or null if there are no current options. 294 * @since 0.7 295 */ 296 public Options getOptions() { 297 Options results = null; 298 if (!optionStack.isEmpty()) { 299 results = (Options) optionStack.peek(); 300 } 301 return results; 302 } 303 304 /** 305 * <p>Pushes the given <code>Options</code> onto the stack. 306 * </p><p> 307 * <strong>Note</strong> that code calling push should ensure that {@link #popOptions} 308 * is called once the options are no longer current. 309 * This ensures that the previous options are reinstated. 310 * </p> 311 * @since 0.7 312 * @param options newly current <code>Options</code>, not null 313 */ 314 public void pushOptions(Options options) { 315 optionStack.push(options); 316 } 317 318 /** 319 * <p>Pops the current options from the stack. 320 * The previously current options (if any exist) 321 * will be reinstated by this method. 322 * </p><p> 323 * <stong>Note</strong> code calling this method should 324 * have previsouly called {@link #popOptions}. 325 * @since 0.7 326 */ 327 public void popOptions() { 328 if (optionStack.isEmpty()) { 329 log.debug("Cannot pop options off empty stack"); 330 } else { 331 optionStack.pop(); 332 } 333 } 334 335 /** 336 * Gets the value of the first option with this name. 337 * The stack of inherited options is search (starting 338 * from the current option) until an option with a non-null 339 * value for the named option is found. 340 * 341 * @param name the name of the option to be found 342 * @return option value or null if this value is never set 343 * @since 0.8 344 */ 345 public String getInheritedOption(String name) { 346 String result = null; 347 for (int i=0; i<optionStack.size() ; i++) 348 { 349 Options options = (Options) optionStack.peek(i); 350 if (options != null) 351 { 352 result = options.getValue(name); 353 if (result != null) 354 { 355 break; 356 } 357 } 358 } 359 return result; 360 } 361 362 }