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.io.read; 018 019 import java.beans.IntrospectionException; 020 021 import org.apache.commons.betwixt.AttributeDescriptor; 022 import org.apache.commons.betwixt.BindingConfiguration; 023 import org.apache.commons.betwixt.ElementDescriptor; 024 import org.apache.commons.betwixt.Options; 025 import org.apache.commons.betwixt.XMLBeanInfo; 026 import org.apache.commons.betwixt.XMLIntrospector; 027 import org.apache.commons.betwixt.expression.Context; 028 import org.apache.commons.betwixt.expression.Updater; 029 import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver; 030 import org.apache.commons.betwixt.strategy.ActionMappingStrategy; 031 import org.apache.commons.collections.ArrayStack; 032 import org.apache.commons.logging.Log; 033 import org.apache.commons.logging.LogFactory; 034 import org.xml.sax.Attributes; 035 036 /** 037 * <p>Extends <code>Context</code> to provide read specific functionality.</p> 038 * <p> 039 * Three stacks are used to manage the reading: 040 * </p> 041 * <ul> 042 * <li><strong>Action mapping stack</strong> contains the {@link MappingAction}'s 043 * used to execute the mapping of the current element and it's ancesters back to the 044 * document root.</li> 045 * <li><strong>Result stack</strong> contains the objects which are bound 046 * to the current element and to each of it's ancester's back to the root</li> 047 * <li><strong>Element mapping stack</strong> records the names of the element 048 * and the classes to which they are bound</li> 049 * </ul> 050 * @author Robert Burrell Donkina 051 * @since 0.5 052 */ 053 public class ReadContext extends Context { 054 ; 055 /** Classloader to be used to load beans during reading */ 056 private ClassLoader classLoader; 057 /** The read specific configuration */ 058 private ReadConfiguration readConfiguration; 059 /** Records the element path together with the locations where classes were mapped*/ 060 private ArrayStack elementMappingStack = new ArrayStack(); 061 /** Contains actions for each element */ 062 private ArrayStack actionMappingStack = new ArrayStack(); 063 /** Stack contains all beans created */ 064 private ArrayStack objectStack = new ArrayStack(); 065 /** Stack contains element descriptors */ 066 private ArrayStack descriptorStack = new ArrayStack(); 067 /** Stack contains updaters */ 068 private ArrayStack updaterStack = new ArrayStack(); 069 070 private Class rootClass; 071 /** The <code>XMLIntrospector</code> to be used to map the xml*/ 072 private XMLIntrospector xmlIntrospector; 073 074 /** 075 * Constructs a <code>ReadContext</code> with the same settings 076 * as an existing <code>Context</code>. 077 * @param context not null 078 * @param readConfiguration not null 079 */ 080 public ReadContext(Context context, ReadConfiguration readConfiguration) { 081 super(context); 082 this.readConfiguration = readConfiguration; 083 } 084 085 /** 086 * Constructs a <code>ReadContext</code> with standard log. 087 * @param bindingConfiguration the dynamic configuration, not null 088 * @param readConfiguration the extra read configuration not null 089 */ 090 public ReadContext( 091 BindingConfiguration bindingConfiguration, 092 ReadConfiguration readConfiguration) { 093 this( 094 LogFactory.getLog(ReadContext.class), 095 bindingConfiguration, 096 readConfiguration); 097 } 098 099 /** 100 * Base constructor 101 * @param log log to this Log 102 * @param bindingConfiguration the dynamic configuration, not null 103 * @param readConfiguration the extra read configuration not null 104 */ 105 public ReadContext( 106 Log log, 107 BindingConfiguration bindingConfiguration, 108 ReadConfiguration readConfiguration) { 109 super(null, log, bindingConfiguration); 110 this.readConfiguration = readConfiguration; 111 } 112 113 /** 114 * Constructs a <code>ReadContext</code> 115 * with the same settings as an existing <code>Context</code>. 116 * @param readContext not null 117 */ 118 public ReadContext(ReadContext readContext) { 119 super(readContext); 120 classLoader = readContext.classLoader; 121 readConfiguration = readContext.readConfiguration; 122 } 123 124 /** 125 * Puts a bean into storage indexed by an (xml) ID. 126 * 127 * @param id the ID string of the xml element associated with the bean 128 * @param bean the Object to store, not null 129 */ 130 public void putBean(String id, Object bean) { 131 getIdMappingStrategy().setReference(this, bean, id); 132 } 133 134 /** 135 * Gets a bean from storage by an (xml) ID. 136 * 137 * @param id the ID string of the xml element associated with the bean 138 * @return the Object that the ID references, otherwise null 139 */ 140 public Object getBean(String id) { 141 return getIdMappingStrategy().getReferenced(this, id); 142 } 143 144 /** 145 * Clears the beans indexed by id. 146 */ 147 public void clearBeans() { 148 getIdMappingStrategy().reset(); 149 } 150 151 /** 152 * Gets the classloader to be used. 153 * @return the classloader that should be used to load all classes, possibly null 154 */ 155 public ClassLoader getClassLoader() { 156 return classLoader; 157 } 158 159 /** 160 * Sets the classloader to be used. 161 * @param classLoader the ClassLoader to be used, possibly null 162 */ 163 public void setClassLoader(ClassLoader classLoader) { 164 this.classLoader = classLoader; 165 } 166 167 /** 168 * Gets the <code>BeanCreationChange</code> to be used to create beans 169 * when an element is mapped. 170 * @return the BeanCreationChain not null 171 */ 172 public BeanCreationChain getBeanCreationChain() { 173 return readConfiguration.getBeanCreationChain(); 174 } 175 176 /** 177 * Gets the strategy used to define default mappings actions 178 * for elements. 179 * @return <code>ActionMappingStrategy</code>. not null 180 */ 181 public ActionMappingStrategy getActionMappingStrategy() { 182 return readConfiguration.getActionMappingStrategy(); 183 } 184 185 /** 186 * Pops the top element from the element mapping stack. 187 * Also removes any mapped class marks below the top element. 188 * 189 * @return the name of the element popped 190 * if there are any more elements on the stack, otherwise null. 191 * This is the local name if the parser is namespace aware, otherwise the name 192 */ 193 public String popElement() { 194 // since the descriptor stack is populated by pushElement, 195 // need to ensure that it's correct popped by popElement 196 if (!descriptorStack.isEmpty()) { 197 descriptorStack.pop(); 198 } 199 200 if (!updaterStack.isEmpty()) { 201 updaterStack.pop(); 202 } 203 204 popOptions(); 205 206 Object top = null; 207 if (!elementMappingStack.isEmpty()) { 208 top = elementMappingStack.pop(); 209 if (top != null) { 210 if (!(top instanceof String)) { 211 return popElement(); 212 } 213 } 214 } 215 216 return (String) top; 217 } 218 219 /** 220 * Gets the element name for the currently mapped element. 221 * @return the name of the currently mapped element, 222 * or null if there has been no element mapped 223 */ 224 public String getCurrentElement() { 225 String result = null; 226 int stackSize = elementMappingStack.size(); 227 int i = 0; 228 while ( i < stackSize ) { 229 Object mappedElement = elementMappingStack.peek(i); 230 if (mappedElement instanceof String) { 231 result = (String) mappedElement; 232 break; 233 } 234 ++i; 235 } 236 return result; 237 } 238 239 /** 240 * Gets the Class that was last mapped, if there is one. 241 * 242 * @return the Class last marked as mapped 243 * or null if no class has been mapped 244 */ 245 public Class getLastMappedClass() { 246 Class lastMapped = null; 247 for (int i = 0, size = elementMappingStack.size(); 248 i < size; 249 i++) { 250 Object entry = elementMappingStack.peek(i); 251 if (entry instanceof Class) { 252 lastMapped = (Class) entry; 253 break; 254 } 255 } 256 return lastMapped; 257 } 258 259 private ElementDescriptor getParentDescriptor() throws IntrospectionException { 260 ElementDescriptor result = null; 261 if (descriptorStack.size() > 1) { 262 result = (ElementDescriptor) descriptorStack.peek(1); 263 } 264 return result; 265 } 266 267 268 /** 269 * Pushes the given element onto the element mapping stack. 270 * 271 * @param elementName the local name if the parser is namespace aware, 272 * otherwise the full element name. Not null 273 */ 274 public void pushElement(String elementName) throws Exception { 275 276 elementMappingStack.push(elementName); 277 // special case to ensure that root class is appropriately marked 278 //TODO: is this really necessary? 279 ElementDescriptor nextDescriptor = null; 280 if (elementMappingStack.size() == 1 && rootClass != null) { 281 markClassMap(rootClass); 282 XMLBeanInfo rootClassInfo 283 = getXMLIntrospector().introspect(rootClass); 284 nextDescriptor = rootClassInfo.getElementDescriptor(); 285 } else { 286 ElementDescriptor currentDescriptor = getCurrentDescriptor(); 287 if (currentDescriptor != null) { 288 nextDescriptor = currentDescriptor.getElementDescriptor(elementName); 289 } 290 } 291 Updater updater = null; 292 Options options = null; 293 if (nextDescriptor != null) { 294 updater = nextDescriptor.getUpdater(); 295 options = nextDescriptor.getOptions(); 296 } 297 updaterStack.push(updater); 298 descriptorStack.push(nextDescriptor); 299 pushOptions(options); 300 } 301 302 /** 303 * Marks the element name stack with a class mapping. 304 * Relative paths and last mapped class are calculated using these marks. 305 * 306 * @param mappedClazz the Class which has been mapped at the current path, not null 307 */ 308 public void markClassMap(Class mappedClazz) throws IntrospectionException { 309 if (mappedClazz.isArray()) { 310 mappedClazz = mappedClazz.getComponentType(); 311 } 312 elementMappingStack.push(mappedClazz); 313 314 XMLBeanInfo mappedClassInfo = getXMLIntrospector().introspect(mappedClazz); 315 ElementDescriptor mappedElementDescriptor = mappedClassInfo.getElementDescriptor(); 316 descriptorStack.push(mappedElementDescriptor); 317 318 Updater updater = mappedElementDescriptor.getUpdater(); 319 updaterStack.push(updater); 320 } 321 322 /** 323 * Pops an action mapping from the stack 324 * @return <code>MappingAction</code>, not null 325 */ 326 public MappingAction popMappingAction() { 327 return (MappingAction) actionMappingStack.pop(); 328 } 329 330 /** 331 * Pushs an action mapping onto the stack 332 * @param mappingAction 333 */ 334 public void pushMappingAction(MappingAction mappingAction) { 335 actionMappingStack.push(mappingAction); 336 } 337 338 /** 339 * Gets the current mapping action 340 * @return MappingAction 341 */ 342 public MappingAction currentMappingAction() { 343 if (actionMappingStack.size() == 0) 344 { 345 return null; 346 } 347 return (MappingAction) actionMappingStack.peek(); 348 } 349 350 public Object getBean() { 351 return objectStack.peek(); 352 } 353 354 public void setBean(Object bean) { 355 // TODO: maybe need to deprecate the set bean method 356 // and push into subclass 357 // for now, do nothing 358 } 359 360 /** 361 * Pops the last mapping <code>Object</code> from the 362 * stack containing beans that have been mapped. 363 * @return the last bean pushed onto the stack 364 */ 365 public Object popBean() { 366 return objectStack.pop(); 367 } 368 369 /** 370 * Pushs a newly mapped <code>Object</code> onto the mapped bean stack. 371 * @param bean 372 */ 373 public void pushBean(Object bean) { 374 objectStack.push(bean); 375 } 376 377 /** 378 * Gets the <code>XMLIntrospector</code> to be used to create 379 * the mappings for the xml. 380 * @return <code>XMLIntrospector</code>, not null 381 */ 382 public XMLIntrospector getXMLIntrospector() { 383 // read context is not intended to be used by multiple threads 384 // so no need to worry about lazy creation 385 if (xmlIntrospector == null) { 386 xmlIntrospector = new XMLIntrospector(); 387 } 388 return xmlIntrospector; 389 } 390 391 /** 392 * Sets the <code>XMLIntrospector</code> to be used to create 393 * the mappings for the xml. 394 * @param xmlIntrospector <code>XMLIntrospector</code>, not null 395 */ 396 public void setXMLIntrospector(XMLIntrospector xmlIntrospector) { 397 this.xmlIntrospector = xmlIntrospector; 398 } 399 400 public Class getRootClass() { 401 return rootClass; 402 } 403 404 public void setRootClass(Class rootClass) { 405 this.rootClass = rootClass; 406 } 407 408 /** 409 * Gets the <code>ElementDescriptor</code> that describes the 410 * mapping for the current element. 411 * @return <code>ElementDescriptor</code> or null if there is no 412 * current mapping 413 * @throws Exception 414 */ 415 public ElementDescriptor getCurrentDescriptor() throws Exception { 416 ElementDescriptor result = null; 417 if (!descriptorStack.empty()) { 418 result = (ElementDescriptor) descriptorStack.peek(); 419 } 420 return result; 421 } 422 423 /** 424 * Populates the object mapped by the <code>AttributeDescriptor</code>s 425 * with the values in the given <code>Attributes</code>. 426 * @param attributeDescriptors <code>AttributeDescriptor</code>s, not null 427 * @param attributes <code>Attributes</code>, not null 428 */ 429 public void populateAttributes( 430 AttributeDescriptor[] attributeDescriptors, 431 Attributes attributes) { 432 433 Log log = getLog(); 434 if (attributeDescriptors != null) { 435 for (int i = 0, size = attributeDescriptors.length; 436 i < size; 437 i++) { 438 AttributeDescriptor attributeDescriptor = 439 attributeDescriptors[i]; 440 441 // The following isn't really the right way to find the attribute 442 // but it's quite robust. 443 // The idea is that you try both namespace and local name first 444 // and if this returns null try the qName. 445 String value = 446 attributes.getValue( 447 attributeDescriptor.getURI(), 448 attributeDescriptor.getLocalName()); 449 450 if (value == null) { 451 value = 452 attributes.getValue( 453 attributeDescriptor.getQualifiedName()); 454 } 455 456 if (log.isTraceEnabled()) { 457 log.trace("Attr URL:" + attributeDescriptor.getURI()); 458 log.trace( 459 "Attr LocalName:" + attributeDescriptor.getLocalName()); 460 log.trace(value); 461 } 462 463 Updater updater = attributeDescriptor.getUpdater(); 464 log.trace(updater); 465 if (updater != null && value != null) { 466 updater.update(this, value); 467 } 468 } 469 } 470 } 471 472 /** 473 * <p>Pushes an <code>Updater</code> onto the stack.</p> 474 * <p> 475 * <strong>Note</strong>Any action pushing an <code>Updater</code> onto 476 * the stack should take responsibility for popping 477 * the updater from the stack at an appropriate time. 478 * </p> 479 * <p> 480 * <strong>Usage:</strong> this may be used by actions 481 * which require a temporary object to be updated. 482 * Pushing an updater onto the stack allow actions 483 * downstream to transparently update the temporary proxy. 484 * </p> 485 * @param updater Updater, possibly null 486 */ 487 public void pushUpdater(Updater updater) { 488 updaterStack.push(updater); 489 } 490 491 /** 492 * Pops the top <code>Updater</code> from the stack. 493 * <p> 494 * <strong>Note</strong>Any action pushing an <code>Updater</code> onto 495 * the stack should take responsibility for popping 496 * the updater from the stack at an appropriate time. 497 * </p> 498 * @return <code>Updater</code>, possibly null 499 */ 500 public Updater popUpdater() { 501 return (Updater) updaterStack.pop(); 502 } 503 504 /** 505 * Gets the current <code>Updater</code>. 506 * This may (or may not) be the updater for the current 507 * descriptor. 508 * If the current descriptor is a bean child, 509 * the the current updater will (most likely) 510 * be the updater for the property. 511 * Actions (that, for example, use proxy objects) 512 * may push updaters onto the stack. 513 * @return Updater, possibly null 514 */ 515 public Updater getCurrentUpdater() { 516 // TODO: think about whether this is right 517 // it makes some sense to look back up the 518 // stack until a non-empty updater is found. 519 // actions who need to put a stock to this 520 // behaviour can always use an ignoring implementation. 521 Updater result = null; 522 if (!updaterStack.empty()) { 523 result = (Updater) updaterStack.peek(); 524 if ( result == null && updaterStack.size() >1 ) { 525 result = (Updater) updaterStack.peek(1); 526 } 527 } 528 return result; 529 } 530 531 /** 532 * Resolves any polymorphism in the element mapping. 533 * @param mapping <code>ElementMapping</code> describing the mapped element 534 * @return <code>null</code> if the type cannot be resolved 535 * or if the current descriptor is not polymorphic 536 * @since 0.8 537 */ 538 public Class resolvePolymorphicType(ElementMapping mapping) { 539 Class result = null; 540 Log log = getLog(); 541 try { 542 ElementDescriptor currentDescriptor = getCurrentDescriptor(); 543 if (currentDescriptor != null) { 544 if (currentDescriptor.isPolymorphic()) { 545 PolymorphicReferenceResolver resolver = getXMLIntrospector().getPolymorphicReferenceResolver(); 546 result = resolver.resolveType(mapping, this); 547 if (result == null) { 548 // try the other polymorphic descriptors 549 ElementDescriptor parent = getParentDescriptor(); 550 if (parent != null) { 551 ElementDescriptor[] descriptors = parent.getElementDescriptors(); 552 ElementDescriptor originalDescriptor = mapping.getDescriptor(); 553 boolean resolved = false; 554 for (int i=0; i<descriptors.length;i++) { 555 ElementDescriptor descriptor = descriptors[i]; 556 if (descriptor.isPolymorphic()) { 557 mapping.setDescriptor(descriptor); 558 result = resolver.resolveType(mapping, this); 559 if (result != null) { 560 resolved = true; 561 descriptorStack.pop(); 562 popOptions(); 563 descriptorStack.push(descriptor); 564 pushOptions(descriptor.getOptions()); 565 Updater originalUpdater = originalDescriptor.getUpdater(); 566 Updater newUpdater = descriptor.getUpdater(); 567 substituteUpdater(originalUpdater, newUpdater); 568 break; 569 } 570 } 571 } 572 if (resolved) { 573 log.debug("Resolved polymorphic type"); 574 } else { 575 log.debug("Failed to resolve polymorphic type"); 576 mapping.setDescriptor(originalDescriptor); 577 } 578 } 579 } 580 } 581 } 582 } catch (Exception e) { 583 log.info("Failed to resolved polymorphic type"); 584 log.debug(mapping, e); 585 } 586 return result; 587 } 588 589 /** 590 * Substitutes one updater in the stack for another. 591 * @param originalUpdater <code>Updater</code> possibly null 592 * @param newUpdater <code>Updater</code> possibly null 593 */ 594 private void substituteUpdater(Updater originalUpdater, Updater newUpdater) { 595 // recursively pop elements off the stack until the first match is found 596 // TODO: may need to consider using custom NILL object and match descriptors 597 if (!updaterStack.isEmpty()) { 598 Updater updater = (Updater) updaterStack.pop(); 599 if (originalUpdater == null && updater == null) { 600 updaterStack.push(newUpdater); 601 } else if (originalUpdater.equals(updater)) { 602 updaterStack.push(newUpdater); 603 } else { 604 substituteUpdater(originalUpdater, newUpdater); 605 updaterStack.push(updater); 606 } 607 } 608 } 609 610 }