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 018 package org.apache.commons.betwixt.io; 019 020 import org.apache.commons.betwixt.BindingConfiguration; 021 import org.apache.commons.betwixt.ElementDescriptor; 022 import org.apache.commons.betwixt.XMLIntrospector; 023 import org.apache.commons.betwixt.expression.Context; 024 import org.apache.commons.betwixt.io.read.BeanBindAction; 025 import org.apache.commons.betwixt.io.read.MappingAction; 026 import org.apache.commons.betwixt.io.read.ReadConfiguration; 027 import org.apache.commons.betwixt.io.read.ReadContext; 028 import org.apache.commons.digester.Digester; 029 import org.apache.commons.digester.Rule; 030 import org.apache.commons.digester.RuleSet; 031 import org.apache.commons.logging.Log; 032 import org.apache.commons.logging.LogFactory; 033 import org.xml.sax.Attributes; 034 035 /** <p>Sets <code>Betwixt</code> digestion rules for a bean class.</p> 036 * 037 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a> 038 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 039 * @since 0.5 040 */ 041 public class BeanRuleSet implements RuleSet { 042 043 /** Logger */ 044 private static Log log = LogFactory.getLog(BeanRuleSet.class); 045 046 /** 047 * Set log to be used by <code>BeanRuleSet</code> instances 048 * @param aLog the <code>Log</code> implementation for this class to log to 049 */ 050 public static void setLog(Log aLog) { 051 log = aLog; 052 } 053 054 /** The base path under which the rules will be attached */ 055 private String basePath; 056 /** The element descriptor for the base */ 057 private ElementDescriptor baseElementDescriptor; 058 /** The (empty) base context from which all Contexts 059 with beans are (directly or indirectly) obtained */ 060 private DigesterReadContext context; 061 /** allows an attribute to be specified to overload the types of beans used */ 062 private String classNameAttribute = "className"; 063 064 /** 065 * Base constructor. 066 * 067 * @param introspector the <code>XMLIntrospector</code> used to introspect 068 * @param basePath specifies the (Digester-style) path under which the rules will be attached 069 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules 070 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created 071 * @param matchIDs should ID/IDREFs be used to match beans? 072 * @deprecated 0.5 use constructor which takes a ReadContext instead 073 */ 074 public BeanRuleSet( 075 XMLIntrospector introspector, 076 String basePath, 077 ElementDescriptor baseElementDescriptor, 078 Class baseBeanClass, 079 boolean matchIDs) { 080 this.basePath = basePath; 081 this.baseElementDescriptor = baseElementDescriptor; 082 BindingConfiguration bindingConfiguration = new BindingConfiguration(); 083 bindingConfiguration.setMapIDs(matchIDs); 084 context = 085 new DigesterReadContext( 086 log, 087 bindingConfiguration, 088 new ReadConfiguration()); 089 context.setRootClass(baseBeanClass); 090 context.setXMLIntrospector(introspector); 091 } 092 093 /** 094 * Base constructor. 095 * 096 * @param introspector the <code>XMLIntrospector</code> used to introspect 097 * @param basePath specifies the (Digester-style) path under which the rules will be attached 098 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules 099 * @param context the root Context that bean carrying Contexts should be obtained from, 100 * not null 101 * @deprecated 0.6 use the constructor which takes a ReadContext instead 102 */ 103 public BeanRuleSet( 104 XMLIntrospector introspector, 105 String basePath, 106 ElementDescriptor baseElementDescriptor, 107 Context context) { 108 109 this.basePath = basePath; 110 this.baseElementDescriptor = baseElementDescriptor; 111 this.context = 112 new DigesterReadContext(context, new ReadConfiguration()); 113 this.context.setRootClass( 114 baseElementDescriptor.getSingularPropertyType()); 115 this.context.setXMLIntrospector(introspector); 116 } 117 118 /** 119 * Base constructor. 120 * 121 * @param introspector the <code>XMLIntrospector</code> used to introspect 122 * @param basePath specifies the (Digester-style) path under which the rules will be attached 123 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules 124 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created 125 * @param context the root Context that bean carrying Contexts should be obtained from, 126 * not null 127 * @deprecated 0.5 use the constructor which takes a ReadContext instead 128 */ 129 public BeanRuleSet( 130 XMLIntrospector introspector, 131 String basePath, 132 ElementDescriptor baseElementDescriptor, 133 Class baseBeanClass, 134 Context context) { 135 this( 136 introspector, 137 basePath, 138 baseElementDescriptor, 139 baseBeanClass, 140 new ReadContext( context, new ReadConfiguration() )); 141 } 142 143 /** 144 * Base constructor. 145 * 146 * @param introspector the <code>XMLIntrospector</code> used to introspect 147 * @param basePath specifies the (Digester-style) path under which the rules will be attached 148 * @param baseElementDescriptor the <code>ElementDescriptor</code> used to create the rules 149 * @param baseBeanClass the <code>Class</code> whose mapping rules will be created 150 * @param baseContext the root Context that bean carrying Contexts should be obtained from, 151 * not null 152 */ 153 public BeanRuleSet( 154 XMLIntrospector introspector, 155 String basePath, 156 ElementDescriptor baseElementDescriptor, 157 Class baseBeanClass, 158 ReadContext baseContext) { 159 this.basePath = basePath; 160 this.baseElementDescriptor = baseElementDescriptor; 161 this.context = new DigesterReadContext(baseContext); 162 this.context.setRootClass(baseBeanClass); 163 this.context.setXMLIntrospector(introspector); 164 } 165 166 /** 167 * The name of the attribute which can be specified in the XML to override the 168 * type of a bean used at a certain point in the schema. 169 * 170 * <p>The default value is 'className'.</p> 171 * 172 * @return The name of the attribute used to overload the class name of a bean 173 */ 174 public String getClassNameAttribute() { 175 return context.getClassNameAttribute(); 176 } 177 178 /** 179 * Sets the name of the attribute which can be specified in 180 * the XML to override the type of a bean used at a certain 181 * point in the schema. 182 * 183 * <p>The default value is 'className'.</p> 184 * 185 * @param classNameAttribute The name of the attribute used to overload the class name of a bean 186 * @deprecated 0.5 set the <code>ReadContext</code> property instead 187 */ 188 public void setClassNameAttribute(String classNameAttribute) { 189 context.setClassNameAttribute(classNameAttribute); 190 } 191 192 //-------------------------------- Ruleset implementation 193 194 /** 195 * <p>Gets the namespace associated with this ruleset.</p> 196 * 197 * <p><strong>Note</strong> namespaces are not currently supported.</p> 198 * 199 * @return null 200 */ 201 public String getNamespaceURI() { 202 return null; 203 } 204 205 /** 206 * Add rules for bean to given <code>Digester</code>. 207 * 208 * @param digester the <code>Digester</code> to which the rules for the bean will be added 209 */ 210 public void addRuleInstances(Digester digester) { 211 if (log.isTraceEnabled()) { 212 log.trace("Adding rules to:" + digester); 213 } 214 215 context.setDigester(digester); 216 217 // if the classloader is not set, set to the digester classloader 218 if (context.getClassLoader() == null) { 219 context.setClassLoader(digester.getClassLoader()); 220 } 221 222 // TODO: need to think about strategy for paths 223 // may need to provide a default path and then allow the user to override 224 digester.addRule("!" + basePath + "/*", new ActionMappingRule()); 225 } 226 227 /** 228 * Single rule that is used to map all elements. 229 * 230 * @author <a href='http://commons.apache.org/'>Apache Commons Team</a> 231 */ 232 private final class ActionMappingRule extends Rule { 233 234 /** 235 * Processes the start of a new <code>Element</code>. 236 * The actual processing is delegated to <code>MappingAction</code>'s. 237 * @see Rule#begin(String, String, Attributes) 238 */ 239 public void begin(String namespace, String name, Attributes attributes) 240 throws Exception { 241 242 if (log.isTraceEnabled()) { 243 int attributesLength = attributes.getLength(); 244 if (attributesLength > 0) { 245 log.trace("Attributes:"); 246 } 247 for (int i = 0, size = attributesLength; i < size; i++) { 248 log.trace("Local:" + attributes.getLocalName(i)); 249 log.trace("URI:" + attributes.getURI(i)); 250 log.trace("QName:" + attributes.getQName(i)); 251 } 252 } 253 254 context.pushElement(name); 255 256 MappingAction nextAction = 257 nextAction(namespace, name, attributes, context); 258 259 context.pushMappingAction(nextAction); 260 } 261 262 /** 263 * Gets the next action to be executed 264 * @param namespace the element's namespace, not null 265 * @param name the element name, not null 266 * @param attributes the element's attributes, not null 267 * @param context the <code>ReadContext</code> against which the xml is being mapped. 268 * @return the initialized <code>MappingAction</code>, not null 269 * @throws Exception 270 */ 271 private MappingAction nextAction( 272 String namespace, 273 String name, 274 Attributes attributes, 275 ReadContext context) 276 throws Exception { 277 278 MappingAction result = null; 279 MappingAction lastAction = context.currentMappingAction(); 280 if (lastAction == null) 281 { 282 result = BeanBindAction.INSTANCE; 283 } else { 284 285 result = lastAction.next(namespace, name, attributes, context); 286 } 287 return result.begin(namespace, name, attributes, context); 288 } 289 290 291 292 /** 293 * Processes the body text for the current element. 294 * This is delegated to the current <code>MappingAction</code>. 295 * @see Rule#body(String, String, String) 296 */ 297 public void body(String namespace, String name, String text) 298 throws Exception { 299 300 if (log.isTraceEnabled()) log.trace("[BRS] Body with text " + text); 301 if (digester.getCount() > 0) { 302 MappingAction action = context.currentMappingAction(); 303 action.body(text, context); 304 } else { 305 log.trace("[BRS] ZERO COUNT"); 306 } 307 } 308 309 /** 310 * Process the end of this element. 311 * This is delegated to the current <code>MappingAction</code>. 312 */ 313 public void end(String namespace, String name) throws Exception { 314 315 MappingAction action = context.popMappingAction(); 316 action.end(context); 317 } 318 319 /** 320 * Tidy up. 321 */ 322 public void finish() { 323 // 324 // Clear indexed beans so that we're ready to process next document 325 // 326 context.clearBeans(); 327 } 328 329 } 330 331 /** 332 * Specialization of <code>ReadContext</code> when reading from <code>Digester</code>. 333 * @author <a href='http://commons.apache.org/'>Apache Commons Team</a> 334 * @version $Revision: 561314 $ 335 */ 336 private static class DigesterReadContext extends ReadContext { 337 338 private Digester digester; 339 340 /** 341 * @param context 342 * @param readConfiguration 343 */ 344 public DigesterReadContext( 345 Context context, 346 ReadConfiguration readConfiguration) { 347 super(context, readConfiguration); 348 // TODO Auto-generated constructor stub 349 } 350 351 /** 352 * @param bindingConfiguration 353 * @param readConfiguration 354 */ 355 public DigesterReadContext( 356 BindingConfiguration bindingConfiguration, 357 ReadConfiguration readConfiguration) { 358 super(bindingConfiguration, readConfiguration); 359 } 360 361 /** 362 * @param log 363 * @param bindingConfiguration 364 * @param readConfiguration 365 */ 366 public DigesterReadContext( 367 Log log, 368 BindingConfiguration bindingConfiguration, 369 ReadConfiguration readConfiguration) { 370 super(log, bindingConfiguration, readConfiguration); 371 } 372 373 /** 374 * @param log 375 * @param bindingConfiguration 376 * @param readConfiguration 377 */ 378 public DigesterReadContext(ReadContext readContext) { 379 super(readContext); 380 } 381 382 public Digester getDigester() { 383 // TODO: replace with something better 384 return digester; 385 } 386 387 public void setDigester(Digester digester) { 388 // TODO: replace once moved to single Rule 389 this.digester = digester; 390 } 391 392 /* (non-Javadoc) 393 * @see org.apache.commons.betwixt.io.read.ReadContext#pushBean(java.lang.Object) 394 */ 395 public void pushBean(Object bean) { 396 super.pushBean(bean); 397 digester.push(bean); 398 } 399 400 /* (non-Javadoc) 401 * @see org.apache.commons.betwixt.io.read.ReadContext#putBean(java.lang.Object) 402 */ 403 public Object popBean() { 404 Object bean = super.popBean(); 405 // don't pop the last from the stack 406 if (digester.getCount() > 0) { 407 digester.pop(); 408 } 409 return bean; 410 } 411 } 412 413 }