JaxBeanInfo.java revision 627:bdb954839363
1/* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.xml.internal.bind.v2.runtime; 27 28import java.io.IOException; 29import java.lang.reflect.InvocationTargetException; 30import java.lang.reflect.Method; 31import java.security.AccessController; 32import java.security.PrivilegedAction; 33import java.util.Arrays; 34import java.util.Collection; 35import java.util.Collections; 36import java.util.logging.Level; 37import java.util.logging.Logger; 38 39import javax.xml.bind.JAXBContext; 40import javax.xml.bind.Marshaller; 41import javax.xml.bind.Unmarshaller; 42import javax.xml.datatype.XMLGregorianCalendar; 43import javax.xml.namespace.QName; 44import javax.xml.stream.XMLStreamException; 45 46import com.sun.istack.internal.NotNull; 47import com.sun.xml.internal.bind.Util; 48import com.sun.xml.internal.bind.v2.model.runtime.RuntimeTypeInfo; 49import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader; 50import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl; 51import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext; 52 53import org.xml.sax.SAXException; 54 55/** 56 * Encapsulates various JAXB operations on objects bound by JAXB. 57 * Immutable and thread-safe. 58 * 59 * <p> 60 * Each JAXB-bound class has a corresponding {@link JaxBeanInfo} object, 61 * which performs all the JAXB related operations on behalf of 62 * the JAXB-bound object. 63 * 64 * <p> 65 * Given a class, the corresponding {@link JaxBeanInfo} can be located 66 * via {@link JAXBContextImpl#getBeanInfo(Class,boolean)}. 67 * 68 * <p> 69 * Typically, {@link JaxBeanInfo} implementations should be generated 70 * by XJC/JXC. Those impl classes will register themselves to their 71 * master {@code ObjectFactory} class. 72 * 73 * <p> 74 * The type parameter BeanT is the Java class of the bean that this represents. 75 * 76 * @author 77 * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 78 */ 79public abstract class JaxBeanInfo<BeanT> { 80 81 protected boolean isNilIncluded = false; 82 83 /** 84 * For {@link JaxBeanInfo} that has multiple type names. 85 */ 86 protected JaxBeanInfo(JAXBContextImpl grammar, RuntimeTypeInfo rti, Class<BeanT> jaxbType, QName[] typeNames, boolean isElement,boolean isImmutable, boolean hasLifecycleEvents) { 87 this(grammar,rti,jaxbType,(Object)typeNames,isElement,isImmutable,hasLifecycleEvents); 88 } 89 90 /** 91 * For {@link JaxBeanInfo} that has one type name. 92 */ 93 protected JaxBeanInfo(JAXBContextImpl grammar, RuntimeTypeInfo rti, Class<BeanT> jaxbType, QName typeName, boolean isElement,boolean isImmutable, boolean hasLifecycleEvents) { 94 this(grammar,rti,jaxbType,(Object)typeName,isElement,isImmutable,hasLifecycleEvents); 95 } 96 97 /** 98 * For {@link JaxBeanInfo} that has no type names. 99 */ 100 protected JaxBeanInfo(JAXBContextImpl grammar, RuntimeTypeInfo rti, Class<BeanT> jaxbType, boolean isElement,boolean isImmutable, boolean hasLifecycleEvents) { 101 this(grammar,rti,jaxbType,(Object)null,isElement,isImmutable,hasLifecycleEvents); 102 } 103 104 private JaxBeanInfo(JAXBContextImpl grammar, RuntimeTypeInfo rti, Class<BeanT> jaxbType, Object typeName, boolean isElement,boolean isImmutable, boolean hasLifecycleEvents) { 105 grammar.beanInfos.put(rti,this); 106 107 this.jaxbType = jaxbType; 108 this.typeName = typeName; 109 this.flag = (short)((isElement?FLAG_IS_ELEMENT:0) 110 |(isImmutable?FLAG_IS_IMMUTABLE:0) 111 |(hasLifecycleEvents?FLAG_HAS_LIFECYCLE_EVENTS:0)); 112 } 113 114 /** 115 * Various boolean flags combined into one field to improve memory footprint. 116 */ 117 protected short flag; 118 119 private static final short FLAG_IS_ELEMENT = 1; 120 private static final short FLAG_IS_IMMUTABLE = 2; 121 private static final short FLAG_HAS_ELEMENT_ONLY_CONTENTMODEL = 4; 122 private static final short FLAG_HAS_BEFORE_UNMARSHAL_METHOD = 8; 123 private static final short FLAG_HAS_AFTER_UNMARSHAL_METHOD = 16; 124 private static final short FLAG_HAS_BEFORE_MARSHAL_METHOD = 32; 125 private static final short FLAG_HAS_AFTER_MARSHAL_METHOD = 64; 126 private static final short FLAG_HAS_LIFECYCLE_EVENTS = 128; 127 128 /** cache of lifecycle methods */ 129 private LifecycleMethods lcm = null; 130 131 /** 132 * True if {@link #jaxbType} has the lifecycle method. 133 */ 134 public final boolean hasBeforeUnmarshalMethod() { 135 return (flag&FLAG_HAS_BEFORE_UNMARSHAL_METHOD) != 0; 136 } 137 138 /** 139 * True if {@link #jaxbType} has the lifecycle method. 140 */ 141 public final boolean hasAfterUnmarshalMethod() { 142 return (flag&FLAG_HAS_AFTER_UNMARSHAL_METHOD) != 0; 143 } 144 145 /** 146 * True if {@link #jaxbType} has the lifecycle method. 147 */ 148 public final boolean hasBeforeMarshalMethod() { 149 return (flag&FLAG_HAS_BEFORE_MARSHAL_METHOD) != 0; 150 } 151 152 /** 153 * True if {@link #jaxbType} has the lifecycle method. 154 */ 155 public final boolean hasAfterMarshalMethod() { 156 return (flag&FLAG_HAS_AFTER_MARSHAL_METHOD) != 0; 157 } 158 159 /** 160 * Gets the JAXB bound class type that this {@link JaxBeanInfo} 161 * handles. 162 * 163 * <p> 164 * IOW, when a bean info object is requested for T, 165 * sometimes the bean info for one of its base classes might be 166 * returned. 167 */ 168 public final Class<BeanT> jaxbType; 169 170 /** 171 * Returns true if the bean is mapped to/from an XML element. 172 * 173 * <p> 174 * When this method returns true, {@link #getElementNamespaceURI(Object)} 175 * and {@link #getElementLocalName(Object)} returns the element name of 176 * the bean. 177 */ 178 public final boolean isElement() { 179 return (flag&FLAG_IS_ELEMENT)!=0; 180 } 181 182 /** 183 * Returns true if the bean is immutable. 184 * 185 * <p> 186 * If this is true, Binder won't try to ueuse this object, and the unmarshaller 187 * won't create a new instance of it before it starts. 188 */ 189 public final boolean isImmutable() { 190 return (flag&FLAG_IS_IMMUTABLE)!=0; 191 } 192 193 /** 194 * True if this bean has an element-only content model. 195 * <p> 196 * If this flag is true, the unmarshaller can work 197 * faster by ignoring whitespaces more efficiently. 198 */ 199 public final boolean hasElementOnlyContentModel() { 200 return (flag&FLAG_HAS_ELEMENT_ONLY_CONTENTMODEL)!=0; 201 } 202 203 /** 204 * True if this bean has an element-only content model. 205 * <p> 206 * Should be considered immutable, though I can't mark it final 207 * because it cannot be computed in this constructor. 208 */ 209 protected final void hasElementOnlyContentModel(boolean value) { 210 if(value) 211 flag |= FLAG_HAS_ELEMENT_ONLY_CONTENTMODEL; 212 else 213 flag &= ~FLAG_HAS_ELEMENT_ONLY_CONTENTMODEL; 214 } 215 216 public boolean isNilIncluded() { 217 return isNilIncluded; 218 } 219 220 /** 221 * This method is used to determine which of the sub-classes should be 222 * interrogated for the existence of lifecycle methods. 223 * 224 * @return true if the un|marshaller should look for lifecycle methods 225 * on this beanInfo, false otherwise. 226 */ 227 public boolean lookForLifecycleMethods() { 228 return (flag&FLAG_HAS_LIFECYCLE_EVENTS)!=0; 229 } 230 231 /** 232 * Returns the namespace URI portion of the element name, 233 * if the bean that this class represents is mapped from/to 234 * an XML element. 235 * 236 * @throws UnsupportedOperationException 237 * if {@link #isElement} is false. 238 */ 239 public abstract String getElementNamespaceURI(BeanT o); 240 241 /** 242 * Returns the local name portion of the element name, 243 * if the bean that this class represents is mapped from/to 244 * an XML element. 245 * 246 * @throws UnsupportedOperationException 247 * if {@link #isElement} is false. 248 */ 249 public abstract String getElementLocalName(BeanT o); 250 251 /** 252 * Type names associated with this {@link JaxBeanInfo}. 253 * 254 * @see #getTypeNames() 255 */ 256 private final Object typeName; // either null, QName, or QName[]. save memory since most of them have just one. 257 258 /** 259 * Returns XML Schema type names if the bean is mapped from 260 * a complex/simple type of XML Schema. 261 * 262 * <p> 263 * This is an ugly necessity to correctly handle 264 * the type substitution semantics of XML Schema. 265 * 266 * <p> 267 * A single Java class maybe mapped to more than one 268 * XML types. All the types listed here are recognized 269 * when we are unmarshalling XML. 270 * 271 * <p> 272 * null if the class is not bound to a named schema type. 273 * 274 * <p> 275 */ 276 public Collection<QName> getTypeNames() { 277 if(typeName==null) return Collections.emptyList(); 278 if(typeName instanceof QName) return Collections.singletonList((QName)typeName); 279 return Arrays.asList((QName[])typeName); 280 } 281 282 /** 283 * Returns the XML type name to be used to marshal the specified instance. 284 * 285 * <P> 286 * Most of the times the type can be determined regardless of the actual 287 * instance, but there's a few exceptions (most notably {@link XMLGregorianCalendar}), 288 * so as a general rule we need an instance to determine it. 289 */ 290 public QName getTypeName(@NotNull BeanT instance) { 291 if(typeName==null) return null; 292 if(typeName instanceof QName) return (QName)typeName; 293 return ((QName[])typeName)[0]; 294 } 295 296 /** 297 * Creates a new instance of the bean. 298 * 299 * <p> 300 * This operation is only supported when {@link #isImmutable} is false. 301 * 302 * @param context 303 * Sometimes the created bean remembers the corresponding source location, 304 */ 305 public abstract BeanT createInstance(UnmarshallingContext context) throws IllegalAccessException, InvocationTargetException, InstantiationException, SAXException; 306 307 /** 308 * Resets the object to the initial state, as if the object 309 * is created fresh. 310 * 311 * <p> 312 * This is used to reuse an existing object for unmarshalling. 313 * 314 * @param context 315 * used for reporting any errors. 316 * 317 * @return 318 * true if the object was successfuly resetted. 319 * False if the object is not resettable, in which case the object will be 320 * discarded and new one will be created. 321 * <p> 322 * If the object is resettable but failed by an error, it should be reported to the context, 323 * then return false. If the object is not resettable to begin with, do not report an error. 324 * 325 * @throws SAXException 326 * as a result of reporting an error, the context may throw a {@link SAXException}. 327 */ 328 public abstract boolean reset( BeanT o, UnmarshallingContext context ) throws SAXException; 329 330 /** 331 * Gets the ID value of the given bean, if it has an ID value. 332 * Otherwise return null. 333 */ 334 public abstract String getId(BeanT o, XMLSerializer target) throws SAXException; 335 336 /** 337 * Serializes child elements and texts into the specified target. 338 */ 339 public abstract void serializeBody( BeanT o, XMLSerializer target ) throws SAXException, IOException, XMLStreamException; 340 341 /** 342 * Serializes attributes into the specified target. 343 */ 344 public abstract void serializeAttributes( BeanT o, XMLSerializer target ) throws SAXException, IOException, XMLStreamException; 345 346 /** 347 * Serializes the bean as the root element. 348 * 349 * <p> 350 * In the java-to-schema binding, an object might marshal in two different 351 * ways depending on whether it is used as the root of the graph or not. 352 * In the former case, an object could marshal as an element, whereas 353 * in the latter case, it marshals as a type. 354 * 355 * <p> 356 * This method is used to marshal the root of the object graph to allow 357 * this semantics to be implemented. 358 * 359 * <p> 360 * It is doubtful to me if it's a good idea for an object to marshal 361 * in two ways depending on the context. 362 * 363 * <p> 364 * For schema-to-java, this is equivalent to {@link #serializeBody(Object, XMLSerializer)}. 365 */ 366 public abstract void serializeRoot( BeanT o, XMLSerializer target ) throws SAXException, IOException, XMLStreamException; 367 368 /** 369 * Declares all the namespace URIs this object is using at 370 * its top-level scope into the specified target. 371 */ 372 public abstract void serializeURIs( BeanT o, XMLSerializer target ) throws SAXException; 373 374 /** 375 * Gets the {@link Loader} that will unmarshall the given object. 376 * 377 * @param context 378 * The {@link JAXBContextImpl} object that governs this object. 379 * This object is taken as a parameter so that {@link JaxBeanInfo} doesn't have 380 * to store them on its own. 381 * 382 * When this method is invoked from within the unmarshaller, tihs parameter can be 383 * null (because the loader is constructed already.) 384 * 385 * @param typeSubstitutionCapable 386 * If true, the returned {@link Loader} is capable of recognizing @xsi:type (if necessary) 387 * and unmarshals a subtype. This allowes an optimization where this bean info 388 * is guaranteed not to have a type substitution. 389 * If false, the returned {@link Loader} doesn't look for @xsi:type. 390 * @return 391 * must return non-null valid object 392 */ 393 public abstract Loader getLoader(JAXBContextImpl context, boolean typeSubstitutionCapable); 394 395 /** 396 * If the bean's representation in XML is just a text, 397 * this method return a {@link Transducer} that lets you convert 398 * values between the text and the bean. 399 */ 400 public abstract Transducer<BeanT> getTransducer(); 401 402 403 /** 404 * Called after all the {@link JaxBeanInfo}s are created. 405 * @param grammar 406 */ 407 protected void link(JAXBContextImpl grammar) { 408 } 409 410 /** 411 * Called at the end of the {@link JAXBContext} initialization phase 412 * to clean up any unnecessary references. 413 */ 414 public void wrapUp() {} 415 416 417 private static final Class[] unmarshalEventParams = { Unmarshaller.class, Object.class }; 418 private static Class[] marshalEventParams = { Marshaller.class }; 419 420 private Method[] getDeclaredMethods(final Class<BeanT> c) { 421 return AccessController.doPrivileged(new PrivilegedAction<Method[]>() { 422 @Override 423 public Method[] run() { 424 return c.getDeclaredMethods(); 425 } 426 }); 427 } 428 429 /** 430 * use reflection to determine which of the 4 object lifecycle methods exist on 431 * the JAXB bound type. 432 */ 433 protected final void setLifecycleFlags() { 434 try { 435 Class<BeanT> jt = jaxbType; 436 437 if (lcm == null) { 438 lcm = new LifecycleMethods(); 439 } 440 441 while (jt != null) { 442 for (Method m : getDeclaredMethods(jt)) { 443 String name = m.getName(); 444 445 if (lcm.beforeUnmarshal == null) { 446 if (name.equals("beforeUnmarshal")) { 447 if (match(m, unmarshalEventParams)) { 448 cacheLifecycleMethod(m, FLAG_HAS_BEFORE_UNMARSHAL_METHOD); 449 } 450 } 451 } 452 453 if (lcm.afterUnmarshal == null) { 454 if (name.equals("afterUnmarshal")) { 455 if (match(m, unmarshalEventParams)) { 456 cacheLifecycleMethod(m, FLAG_HAS_AFTER_UNMARSHAL_METHOD); 457 } 458 } 459 } 460 461 if (lcm.beforeMarshal == null) { 462 if (name.equals("beforeMarshal")) { 463 if (match(m, marshalEventParams)) { 464 cacheLifecycleMethod(m, FLAG_HAS_BEFORE_MARSHAL_METHOD); 465 } 466 } 467 } 468 469 if (lcm.afterMarshal == null) { 470 if (name.equals("afterMarshal")) { 471 if (match(m, marshalEventParams)) { 472 cacheLifecycleMethod(m, FLAG_HAS_AFTER_MARSHAL_METHOD); 473 } 474 } 475 } 476 } 477 jt = (Class<BeanT>) jt.getSuperclass(); 478 } 479 } catch (SecurityException e) { 480 // this happens when we don't have enough permission. 481 logger.log(Level.WARNING, Messages.UNABLE_TO_DISCOVER_EVENTHANDLER.format( 482 jaxbType.getName(), e), e); 483 } 484 } 485 486 private boolean match(Method m, Class[] params) { 487 return Arrays.equals(m.getParameterTypes(),params); 488 } 489 490 /** 491 * Cache a reference to the specified lifecycle method for the jaxbType 492 * associated with this beanInfo. 493 * 494 * @param m Method reference 495 * @param lifecycleFlag byte representing which of the 4 lifecycle methods 496 * is being cached 497 */ 498 private void cacheLifecycleMethod(Method m, short lifecycleFlag) { 499 //LifecycleMethods lcm = getLifecycleMethods(); 500 if(lcm==null) { 501 lcm = new LifecycleMethods(); 502 //lcmCache.put(jaxbType, lcm); 503 } 504 505 m.setAccessible(true); 506 507 flag |= lifecycleFlag; 508 509 switch (lifecycleFlag) { 510 case FLAG_HAS_BEFORE_UNMARSHAL_METHOD: 511 lcm.beforeUnmarshal = m; 512 break; 513 case FLAG_HAS_AFTER_UNMARSHAL_METHOD: 514 lcm.afterUnmarshal = m; 515 break; 516 case FLAG_HAS_BEFORE_MARSHAL_METHOD: 517 lcm.beforeMarshal = m; 518 break; 519 case FLAG_HAS_AFTER_MARSHAL_METHOD: 520 lcm.afterMarshal = m; 521 break; 522 } 523 } 524 525 /** 526 * Return the LifecycleMethods cache for this ClassBeanInfo's corresponding 527 * jaxbType if it exists, else return null. 528 * 529 */ 530 public final LifecycleMethods getLifecycleMethods() { 531 return lcm; 532 } 533 534 /** 535 * Invokes the beforeUnmarshal method if applicable. 536 */ 537 public final void invokeBeforeUnmarshalMethod(UnmarshallerImpl unm, Object child, Object parent) throws SAXException { 538 Method m = getLifecycleMethods().beforeUnmarshal; 539 invokeUnmarshallCallback(m, child, unm, parent); 540 } 541 542 /** 543 * Invokes the afterUnmarshal method if applicable. 544 */ 545 public final void invokeAfterUnmarshalMethod(UnmarshallerImpl unm, Object child, Object parent) throws SAXException { 546 Method m = getLifecycleMethods().afterUnmarshal; 547 invokeUnmarshallCallback(m, child, unm, parent); 548 } 549 550 private void invokeUnmarshallCallback(Method m, Object child, UnmarshallerImpl unm, Object parent) throws SAXException { 551 try { 552 m.invoke(child,unm,parent); 553 } catch (IllegalAccessException e) { 554 UnmarshallingContext.getInstance().handleError(e, false); 555 } catch (InvocationTargetException e) { 556 UnmarshallingContext.getInstance().handleError(e, false); 557 } 558 } 559 560 private static final Logger logger = Util.getClassLogger(); 561} 562