/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2014 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.xml.bind.v2.runtime; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.ValidationEvent; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.helpers.ValidationEventImpl; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import com.sun.istack.FinalArrayList; import com.sun.xml.bind.Util; import com.sun.xml.bind.api.AccessorException; import com.sun.xml.bind.v2.ClassFactory; import com.sun.xml.bind.v2.WellKnownNamespace; import com.sun.xml.bind.v2.model.core.ID; import com.sun.xml.bind.v2.model.runtime.RuntimeClassInfo; import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo; import com.sun.xml.bind.v2.runtime.property.AttributeProperty; import com.sun.xml.bind.v2.runtime.property.Property; import com.sun.xml.bind.v2.runtime.property.PropertyFactory; import com.sun.xml.bind.v2.runtime.reflect.Accessor; import com.sun.xml.bind.v2.runtime.unmarshaller.Loader; import com.sun.xml.bind.v2.runtime.unmarshaller.StructureLoader; import com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext; import com.sun.xml.bind.v2.runtime.unmarshaller.XsiTypeLoader; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.LocatorImpl; /** * {@link JaxBeanInfo} implementation for j2s bean. * * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) */ public final class ClassBeanInfoImpl<BeanT> extends JaxBeanInfo<BeanT> implements AttributeAccessor<BeanT> { /** * Properties of this bean class but not its ancestor classes. */ public final Property<BeanT>[] properties; /** * Non-null if this bean has an ID property. */ private Property<? super BeanT> idProperty; /** * Immutable configured loader for this class. * * <p> * Set from the link method, but considered final. */ private Loader loader; private Loader loaderWithTypeSubst; /** * Set only until the link phase to avoid leaking memory. */ private RuntimeClassInfo ci; private final Accessor<? super BeanT,Map<QName,String>> inheritedAttWildcard; private final Transducer<BeanT> xducer; /** * {@link ClassBeanInfoImpl} that represents the super class of {@link #jaxbType}. */ public final ClassBeanInfoImpl<? super BeanT> superClazz; private final Accessor<? super BeanT,Locator> xmlLocatorField; private final Name tagName; private boolean retainPropertyInfo = false; /** * The {@link AttributeProperty}s for this type and all its ancestors. * If {@link JAXBContextImpl#c14nSupport} is true, this is sorted alphabetically. */ private /*final*/ AttributeProperty<BeanT>[] attributeProperties; /** * {@link Property}s that need to receive {@link Property#serializeURIs(Object, XMLSerializer)} callback. */ private /*final*/ Property<BeanT>[] uriProperties; private final Method factoryMethod; /*package*/ ClassBeanInfoImpl(JAXBContextImpl owner, RuntimeClassInfo ci) { super(owner,ci,ci.getClazz(),ci.getTypeName(),ci.isElement(),false,true); this.ci = ci; this.inheritedAttWildcard = ci.getAttributeWildcard(); this.xducer = ci.getTransducer(); this.factoryMethod = ci.getFactoryMethod(); this.retainPropertyInfo = owner.retainPropertyInfo; // make the factory accessible if(factoryMethod!=null) { int classMod = factoryMethod.getDeclaringClass().getModifiers(); if(!Modifier.isPublic(classMod) || !Modifier.isPublic(factoryMethod.getModifiers())) { // attempt to make it work even if the constructor is not accessible try { factoryMethod.setAccessible(true); } catch(SecurityException e) { // but if we don't have a permission to do so, work gracefully. logger.log(Level.FINE,"Unable to make the method of "+factoryMethod+" accessible",e); throw e; } } } if(ci.getBaseClass()==null) this.superClazz = null; else this.superClazz = owner.getOrCreate(ci.getBaseClass()); if(superClazz!=null && superClazz.xmlLocatorField!=null) xmlLocatorField = superClazz.xmlLocatorField; else xmlLocatorField = ci.getLocatorField(); // create property objects Collection<? extends RuntimePropertyInfo> ps = ci.getProperties(); this.properties = new Property[ps.size()]; int idx=0; boolean elementOnly = true; for( RuntimePropertyInfo info : ps ) { Property p = PropertyFactory.create(owner,info); if(info.id()==ID.ID) idProperty = p; properties[idx++] = p; elementOnly &= info.elementOnlyContent(); checkOverrideProperties(p); } // super class' idProperty might not be computed at this point, // so check that later hasElementOnlyContentModel( elementOnly ); // again update this value later when we know that of the super class if(ci.isElement()) tagName = owner.nameBuilder.createElementName(ci.getElementName()); else tagName = null; setLifecycleFlags(); } private void checkOverrideProperties(Property p) { ClassBeanInfoImpl bi = this; while ((bi = bi.superClazz) != null) { Property[] props = bi.properties; if (props == null) break; for (Property superProperty : props) { if (superProperty != null) { String spName = superProperty.getFieldName(); if ((spName != null) && (spName.equals(p.getFieldName()))) { superProperty.setHiddenByOverride(true); } } } } } @Override protected void link(JAXBContextImpl grammar) { if(uriProperties!=null) return; // avoid linking twice super.link(grammar); if(superClazz!=null) superClazz.link(grammar); getLoader(grammar,true); // make sure to build the loader if we haven't done so. // propagate values from super class if(superClazz!=null) { if(idProperty==null) idProperty = superClazz.idProperty; if(!superClazz.hasElementOnlyContentModel()) hasElementOnlyContentModel(false); } // create a list of attribute/URI handlers List<AttributeProperty> attProps = new FinalArrayList<AttributeProperty>(); List<Property> uriProps = new FinalArrayList<Property>(); for (ClassBeanInfoImpl bi = this; bi != null; bi = bi.superClazz) { for (int i = 0; i < bi.properties.length; i++) { Property p = bi.properties[i]; if(p instanceof AttributeProperty) attProps.add((AttributeProperty) p); if(p.hasSerializeURIAction()) uriProps.add(p); } } if(grammar.c14nSupport) Collections.sort(attProps); if(attProps.isEmpty()) attributeProperties = EMPTY_PROPERTIES; else attributeProperties = attProps.toArray(new AttributeProperty[attProps.size()]); if(uriProps.isEmpty()) uriProperties = EMPTY_PROPERTIES; else uriProperties = uriProps.toArray(new Property[uriProps.size()]); } @Override public void wrapUp() { for (Property p : properties) p.wrapUp(); ci = null; super.wrapUp(); } public String getElementNamespaceURI(BeanT bean) { return tagName.nsUri; } public String getElementLocalName(BeanT bean) { return tagName.localName; } public BeanT createInstance(UnmarshallingContext context) throws IllegalAccessException, InvocationTargetException, InstantiationException, SAXException { BeanT bean = null; if (factoryMethod == null){ bean = ClassFactory.create0(jaxbType); }else { Object o = ClassFactory.create(factoryMethod); if( jaxbType.isInstance(o) ){ bean = (BeanT)o; } else { throw new InstantiationException("The factory method didn't return a correct object"); } } if(xmlLocatorField!=null) // need to copy because Locator is mutable try { xmlLocatorField.set(bean,new LocatorImpl(context.getLocator())); } catch (AccessorException e) { context.handleError(e); } return bean; } public boolean reset(BeanT bean, UnmarshallingContext context) throws SAXException { try { if(superClazz!=null) superClazz.reset(bean,context); for( Property<BeanT> p : properties ) p.reset(bean); return true; } catch (AccessorException e) { context.handleError(e); return false; } } public String getId(BeanT bean, XMLSerializer target) throws SAXException { if(idProperty!=null) { try { return idProperty.getIdValue(bean); } catch (AccessorException e) { target.reportError(null,e); } } return null; } public void serializeRoot(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException { if(tagName==null) { Class beanClass = bean.getClass(); String message; if (beanClass.isAnnotationPresent(XmlRootElement.class)) { message = Messages.UNABLE_TO_MARSHAL_UNBOUND_CLASS.format(beanClass.getName()); } else { message = Messages.UNABLE_TO_MARSHAL_NON_ELEMENT.format(beanClass.getName()); } target.reportError(new ValidationEventImpl(ValidationEvent.ERROR,message,null, null)); } else { target.startElement(tagName,bean); target.childAsSoleContent(bean,null); target.endElement(); if (retainPropertyInfo) { target.currentProperty.remove(); } } } public void serializeBody(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException { if (superClazz != null) { superClazz.serializeBody(bean, target); } try { for (Property<BeanT> p : properties) { if (retainPropertyInfo) { target.currentProperty.set(p); } boolean isThereAnOverridingProperty = p.isHiddenByOverride(); if (!isThereAnOverridingProperty || bean.getClass().equals(jaxbType)) { p.serializeBody(bean, target, null); } else if (isThereAnOverridingProperty) { // need to double check the override - it should be safe to do after the model has been created because it's targeted to override properties only Class beanClass = bean.getClass(); if (Utils.REFLECTION_NAVIGATOR.getDeclaredField(beanClass, p.getFieldName()) == null) { p.serializeBody(bean, target, null); } } } } catch (AccessorException e) { target.reportError(null, e); } } public void serializeAttributes(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException { for( AttributeProperty<BeanT> p : attributeProperties ) try { if (retainPropertyInfo) { final Property parentProperty = target.getCurrentProperty(); target.currentProperty.set(p); p.serializeAttributes(bean,target); target.currentProperty.set(parentProperty); } else { p.serializeAttributes(bean,target); } if (p.attName.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE, "nil")) { isNilIncluded = true; } } catch (AccessorException e) { target.reportError(null,e); } try { if(inheritedAttWildcard!=null) { Map<QName,String> map = inheritedAttWildcard.get(bean); target.attWildcardAsAttributes(map,null); } } catch (AccessorException e) { target.reportError(null,e); } } public void serializeURIs(BeanT bean, XMLSerializer target) throws SAXException { try { if (retainPropertyInfo) { final Property parentProperty = target.getCurrentProperty(); for( Property<BeanT> p : uriProperties ) { target.currentProperty.set(p); p.serializeURIs(bean,target); } target.currentProperty.set(parentProperty); } else { for( Property<BeanT> p : uriProperties ) { p.serializeURIs(bean,target); } } if(inheritedAttWildcard!=null) { Map<QName,String> map = inheritedAttWildcard.get(bean); target.attWildcardAsURIs(map,null); } } catch (AccessorException e) { target.reportError(null,e); } } public Loader getLoader(JAXBContextImpl context, boolean typeSubstitutionCapable) { if(loader==null) { // these variables have to be set before they are initialized, // because the initialization may build other loaders and they may refer to this. StructureLoader sl = new StructureLoader(this); loader = sl; if(ci.hasSubClasses()) loaderWithTypeSubst = new XsiTypeLoader(this); else // optimization. we know there can be no @xsi:type loaderWithTypeSubst = loader; sl.init(context,this,ci.getAttributeWildcard()); } if(typeSubstitutionCapable) return loaderWithTypeSubst; else return loader; } public Transducer<BeanT> getTransducer() { return xducer; } private static final AttributeProperty[] EMPTY_PROPERTIES = new AttributeProperty[0]; private static final Logger logger = Util.getClassLogger(); }