| /* |
| * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0, which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.glassfish.jaxb.runtime.v2.runtime; |
| |
| import com.sun.istack.NotNull; |
| import com.sun.istack.Pool; |
| import org.glassfish.jaxb.core.api.ErrorListener; |
| import org.glassfish.jaxb.runtime.api.*; |
| import org.glassfish.jaxb.core.unmarshaller.DOMScanner; |
| import org.glassfish.jaxb.core.util.Which; |
| import org.glassfish.jaxb.core.v2.WellKnownNamespace; |
| import org.glassfish.jaxb.runtime.v2.model.annotation.RuntimeAnnotationReader; |
| import org.glassfish.jaxb.runtime.v2.model.annotation.RuntimeInlineAnnotationReader; |
| import org.glassfish.jaxb.core.v2.model.core.Adapter; |
| import org.glassfish.jaxb.core.v2.model.core.NonElement; |
| import org.glassfish.jaxb.core.v2.model.core.Ref; |
| import org.glassfish.jaxb.runtime.v2.model.impl.RuntimeBuiltinLeafInfoImpl; |
| import org.glassfish.jaxb.runtime.v2.model.impl.RuntimeModelBuilder; |
| import org.glassfish.jaxb.core.v2.model.nav.Navigator; |
| import org.glassfish.jaxb.core.v2.runtime.RuntimeUtil; |
| import org.glassfish.jaxb.runtime.v2.runtime.output.Encoded; |
| import org.glassfish.jaxb.runtime.v2.runtime.property.AttributeProperty; |
| import org.glassfish.jaxb.runtime.v2.runtime.property.Property; |
| import org.glassfish.jaxb.runtime.v2.runtime.reflect.Accessor; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.Loader; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.TagName; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallerImpl; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallingContext; |
| import org.glassfish.jaxb.runtime.v2.schemagen.XmlSchemaGenerator; |
| import org.glassfish.jaxb.core.v2.util.EditDistance; |
| import org.glassfish.jaxb.runtime.v2.util.QNameMap; |
| import org.glassfish.jaxb.core.v2.util.XmlFactory; |
| import com.sun.xml.txw2.output.ResultFactory; |
| import jakarta.xml.bind.*; |
| import jakarta.xml.bind.annotation.XmlAttachmentRef; |
| import jakarta.xml.bind.annotation.XmlList; |
| import jakarta.xml.bind.annotation.XmlNs; |
| import jakarta.xml.bind.annotation.XmlSchema; |
| import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; |
| import org.glassfish.jaxb.runtime.v2.model.runtime.*; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| import javax.xml.namespace.QName; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.FactoryConfigurationError; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.transform.Result; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.sax.SAXTransformerFactory; |
| import javax.xml.transform.sax.TransformerHandler; |
| import java.io.IOException; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Type; |
| import java.util.*; |
| import java.util.Map.Entry; |
| |
| /** |
| * This class provides the implementation of JAXBContext. |
| * |
| */ |
| public final class JAXBContextImpl extends JAXBRIContext { |
| |
| /** |
| * All the bridge classes. |
| */ |
| private final Map<TypeReference, Bridge> bridges = new LinkedHashMap<TypeReference,Bridge>(); |
| |
| /** |
| * Shared instance of {@link DocumentBuilder}. |
| * Lock before use. Lazily created. |
| */ |
| private static DocumentBuilder db; |
| |
| private final QNameMap<JaxBeanInfo> rootMap = new QNameMap<JaxBeanInfo>(); |
| private final HashMap<QName,JaxBeanInfo> typeMap = new HashMap<QName,JaxBeanInfo>(); |
| |
| /** |
| * Map from JAXB-bound {@link Class} to its {@link JaxBeanInfo}. |
| */ |
| private final Map<Class,JaxBeanInfo> beanInfoMap = new LinkedHashMap<Class,JaxBeanInfo>(); |
| |
| /** |
| * All created {@link JaxBeanInfo}s. |
| * Updated from each {@link JaxBeanInfo}s constructors to avoid infinite recursion |
| * for a cyclic reference. |
| * |
| * <p> |
| * This map is only used while the {@link JAXBContextImpl} is built and set to null |
| * to avoid keeping references too long. |
| */ |
| protected Map<RuntimeTypeInfo,JaxBeanInfo> beanInfos = new LinkedHashMap<RuntimeTypeInfo, JaxBeanInfo>(); |
| |
| private final Map<Class/*scope*/,Map<QName,ElementBeanInfoImpl>> elements = new LinkedHashMap<Class, Map<QName, ElementBeanInfoImpl>>(); |
| |
| /** |
| * Pool of {@link Marshaller}s. |
| */ |
| public final Pool<Marshaller> marshallerPool = new Pool.Impl<Marshaller>() { |
| protected @NotNull Marshaller create() { |
| return createMarshaller(); |
| } |
| }; |
| |
| public final Pool<Unmarshaller> unmarshallerPool = new Pool.Impl<Unmarshaller>() { |
| protected @NotNull Unmarshaller create() { |
| return createUnmarshaller(); |
| } |
| }; |
| |
| /** |
| * Used to assign indices to known names in this grammar. |
| * Reset to null once the build phase is completed. |
| */ |
| public NameBuilder nameBuilder = new NameBuilder(); |
| |
| /** |
| * Keeps the list of known names. |
| * This field is set once the build pahse is completed. |
| */ |
| public final NameList nameList; |
| |
| /** |
| * Input to the JAXBContext.newInstance, so that we can recreate |
| * {@link RuntimeTypeInfoSet} whenever we need. |
| */ |
| private final String defaultNsUri; |
| private final Class[] classes; |
| |
| /** |
| * true to reorder attributes lexicographically in preparation of the c14n support. |
| */ |
| protected final boolean c14nSupport; |
| |
| /** |
| * Flag that user has provided a custom AccessorFactory for JAXB to use |
| */ |
| public final boolean xmlAccessorFactorySupport; |
| |
| /** |
| * @see JAXBRIContext#TREAT_EVERYTHING_NILLABLE |
| */ |
| public final boolean allNillable; |
| |
| /** |
| * Store properties, so that they can be recovered in the run (is here because of JSON encoding of Jersey). |
| */ |
| public final boolean retainPropertyInfo; |
| |
| /** |
| * Suppress reflection accessor warnings. |
| */ |
| public final boolean supressAccessorWarnings; |
| |
| /** |
| * Improved xsi type handling. |
| */ |
| public final boolean improvedXsiTypeHandling; |
| |
| /** |
| * Disable security processing. |
| */ |
| public final boolean disableSecurityProcessing; |
| |
| private WeakReference<RuntimeTypeInfoSet> typeInfoSetCache; |
| |
| private @NotNull |
| RuntimeAnnotationReader annotationReader; |
| |
| private /*almost final*/ boolean hasSwaRef; |
| private final @NotNull Map<Class,Class> subclassReplacements; |
| |
| /** |
| * If true, we aim for faster {@link JAXBContext} instantiation performance, |
| * instead of going after efficient sustained unmarshalling/marshalling performance. |
| * |
| * @since 2.0.4 |
| */ |
| public final boolean fastBoot; |
| |
| private Set<XmlNs> xmlNsSet = null; |
| |
| /** |
| * If true, despite the specification, unmarshall child element with parent namespace, if child namespace is not specified. |
| * The default value is null for System {@code org.glassfish.jaxb.backupWithParentNamespace} property to be used, |
| * and false is assumed if it's not set either. |
| * |
| * Boolean |
| * @since 2.3.0 |
| */ |
| public Boolean backupWithParentNamespace = null; |
| |
| /** |
| * The maximum number of errors unmarshall operation reports. Use negative value to report all errors. |
| * The default value is 10. |
| * |
| * @since 2.3.3 |
| */ |
| public final int maxErrorsCount; |
| |
| /** |
| * Returns declared XmlNs annotations (from package-level annotation XmlSchema |
| * |
| * @return set of all present XmlNs annotations |
| */ |
| public Set<XmlNs> getXmlNsSet() { |
| return xmlNsSet; |
| } |
| |
| private JAXBContextImpl(JAXBContextBuilder builder) throws JAXBException { |
| |
| this.defaultNsUri = builder.defaultNsUri; |
| this.retainPropertyInfo = builder.retainPropertyInfo; |
| this.annotationReader = builder.annotationReader; |
| this.subclassReplacements = builder.subclassReplacements; |
| this.c14nSupport = builder.c14nSupport; |
| this.classes = builder.classes; |
| this.xmlAccessorFactorySupport = builder.xmlAccessorFactorySupport; |
| this.allNillable = builder.allNillable; |
| this.supressAccessorWarnings = builder.supressAccessorWarnings; |
| this.improvedXsiTypeHandling = builder.improvedXsiTypeHandling; |
| this.disableSecurityProcessing = builder.disableSecurityProcessing; |
| this.backupWithParentNamespace = builder.backupWithParentNamespace; |
| this.maxErrorsCount = builder.maxErrorsCount; |
| |
| Collection<TypeReference> typeRefs = builder.typeRefs; |
| |
| boolean fastB; |
| try { |
| fastB = Boolean.getBoolean(JAXBContextImpl.class.getName()+".fastBoot"); |
| } catch (SecurityException e) { |
| fastB = false; |
| } |
| this.fastBoot = fastB; |
| |
| RuntimeTypeInfoSet typeSet = getTypeInfoSet(); |
| |
| // at least prepare the empty table so that we don't have to check for null later |
| elements.put(null,new LinkedHashMap<QName, ElementBeanInfoImpl>()); |
| |
| // recognize leaf bean infos |
| for( RuntimeBuiltinLeafInfo leaf : RuntimeBuiltinLeafInfoImpl.builtinBeanInfos ) { |
| LeafBeanInfoImpl<?> bi = new LeafBeanInfoImpl(this,leaf); |
| beanInfoMap.put(leaf.getClazz(),bi); |
| for( QName t : bi.getTypeNames() ) |
| typeMap.put(t,bi); |
| } |
| |
| for (RuntimeEnumLeafInfo e : typeSet.enums().values()) { |
| JaxBeanInfo<?> bi = getOrCreate(e); |
| for (QName qn : bi.getTypeNames()) |
| typeMap.put( qn, bi ); |
| if(e.isElement()) |
| rootMap.put( e.getElementName(), bi ); |
| } |
| |
| for (RuntimeArrayInfo a : typeSet.arrays().values()) { |
| JaxBeanInfo<?> ai = getOrCreate(a); |
| for (QName qn : ai.getTypeNames()) |
| typeMap.put( qn, ai ); |
| } |
| |
| for( Entry<Class, ? extends RuntimeClassInfo> e : typeSet.beans().entrySet() ) { |
| ClassBeanInfoImpl<?> bi = getOrCreate(e.getValue()); |
| |
| XmlSchema xs = this.annotationReader.getPackageAnnotation(XmlSchema.class, e.getKey(), null); |
| if(xs != null) { |
| if(xs.xmlns() != null && xs.xmlns().length > 0) { |
| if(xmlNsSet == null) |
| xmlNsSet = new HashSet<XmlNs>(); |
| xmlNsSet.addAll(Arrays.asList(xs.xmlns())); |
| } |
| } |
| |
| if(bi.isElement()) |
| rootMap.put( e.getValue().getElementName(), bi ); |
| |
| for (QName qn : bi.getTypeNames()) |
| typeMap.put( qn, bi ); |
| } |
| |
| // fill in element mappings |
| for( RuntimeElementInfo n : typeSet.getAllElements() ) { |
| ElementBeanInfoImpl bi = getOrCreate(n); |
| if(n.getScope()==null) |
| rootMap.put(n.getElementName(),bi); |
| |
| RuntimeClassInfo scope = n.getScope(); |
| Class scopeClazz = scope==null?null:scope.getClazz(); |
| Map<QName,ElementBeanInfoImpl> m = elements.get(scopeClazz); |
| if(m==null) { |
| m = new LinkedHashMap<QName, ElementBeanInfoImpl>(); |
| elements.put(scopeClazz,m); |
| } |
| m.put(n.getElementName(),bi); |
| } |
| |
| // this one is so that we can handle plain JAXBElements. |
| beanInfoMap.put(JAXBElement.class,new ElementBeanInfoImpl(this)); |
| // another special BeanInfoImpl just for marshalling |
| beanInfoMap.put(CompositeStructure.class,new CompositeStructureBeanInfo(this)); |
| |
| getOrCreate(typeSet.getAnyTypeInfo()); |
| |
| // then link them all! |
| for (JaxBeanInfo bi : beanInfos.values()) |
| bi.link(this); |
| |
| // register primitives for boxed types just to make GrammarInfo fool-proof |
| for( Map.Entry<Class,Class> e : RuntimeUtil.primitiveToBox.entrySet() ) |
| beanInfoMap.put( e.getKey(), beanInfoMap.get(e.getValue()) ); |
| |
| // build bridges |
| Navigator<Type, Class, Field, Method> nav = typeSet.getNavigator(); |
| |
| for (TypeReference tr : typeRefs) { |
| XmlJavaTypeAdapter xjta = tr.get(XmlJavaTypeAdapter.class); |
| Adapter<Type,Class> a=null; |
| XmlList xl = tr.get(XmlList.class); |
| |
| // eventually compute the in-memory type |
| Class erasedType = (Class) nav.erasure(tr.type); |
| |
| if(xjta!=null) { |
| a = new Adapter<Type,Class>(xjta.value(),nav); |
| } |
| if(tr.get(XmlAttachmentRef.class)!=null) { |
| a = new Adapter<Type,Class>(SwaRefAdapter.class,nav); |
| hasSwaRef = true; |
| } |
| |
| if(a!=null) { |
| erasedType = (Class) nav.erasure(a.defaultType); |
| } |
| |
| Name name = nameBuilder.createElementName(tr.tagName); |
| |
| InternalBridge bridge; |
| if(xl==null) |
| bridge = new BridgeImpl(this, name,getBeanInfo(erasedType,true),tr); |
| else |
| bridge = new BridgeImpl(this, name,new ValueListBeanInfoImpl(this,erasedType),tr); |
| |
| if(a!=null) |
| bridge = new BridgeAdapter(bridge,a.adapterType); |
| |
| bridges.put(tr,bridge); |
| } |
| |
| this.nameList = nameBuilder.conclude(); |
| |
| for (JaxBeanInfo bi : beanInfos.values()) |
| bi.wrapUp(); |
| |
| // no use for them now |
| nameBuilder = null; |
| beanInfos = null; |
| } |
| |
| /** |
| * True if this JAXBContext has {@link XmlAttachmentRef}. |
| */ |
| public boolean hasSwaRef() { |
| return hasSwaRef; |
| } |
| |
| public RuntimeTypeInfoSet getRuntimeTypeInfoSet() { |
| try { |
| return getTypeInfoSet(); |
| } catch (IllegalAnnotationsException e) { |
| // impossible, once the model is constructred |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Creates a {@link RuntimeTypeInfoSet}. |
| */ |
| public RuntimeTypeInfoSet getTypeInfoSet() throws IllegalAnnotationsException { |
| |
| // check cache |
| if(typeInfoSetCache!=null) { |
| RuntimeTypeInfoSet r = typeInfoSetCache.get(); |
| if(r!=null) |
| return r; |
| } |
| |
| final RuntimeModelBuilder builder = new RuntimeModelBuilder(this,annotationReader,subclassReplacements,defaultNsUri); |
| |
| IllegalAnnotationsException.Builder errorHandler = new IllegalAnnotationsException.Builder(); |
| builder.setErrorHandler(errorHandler); |
| |
| for( Class c : classes ) { |
| if(c==CompositeStructure.class) |
| // CompositeStructure doesn't have TypeInfo, so skip it. |
| // We'll add JaxBeanInfo for this later automatically |
| continue; |
| builder.getTypeInfo(new Ref<Type,Class>(c)); |
| } |
| |
| this.hasSwaRef |= builder.hasSwaRef; |
| RuntimeTypeInfoSet r = builder.link(); |
| |
| errorHandler.check(); |
| assert r!=null : "if no error was reported, the link must be a success"; |
| |
| typeInfoSetCache = new WeakReference<RuntimeTypeInfoSet>(r); |
| |
| return r; |
| } |
| |
| |
| public ElementBeanInfoImpl getElement(Class scope, QName name) { |
| Map<QName,ElementBeanInfoImpl> m = elements.get(scope); |
| if(m!=null) { |
| ElementBeanInfoImpl bi = m.get(name); |
| if(bi!=null) |
| return bi; |
| } |
| m = elements.get(null); |
| return m.get(name); |
| } |
| |
| |
| |
| |
| |
| private ElementBeanInfoImpl getOrCreate( RuntimeElementInfo rei ) { |
| JaxBeanInfo bi = beanInfos.get(rei); |
| if(bi!=null) return (ElementBeanInfoImpl)bi; |
| |
| // all elements share the same type, so we can't register them to beanInfoMap |
| return new ElementBeanInfoImpl(this, rei); |
| } |
| |
| protected JaxBeanInfo getOrCreate( RuntimeEnumLeafInfo eli ) { |
| JaxBeanInfo bi = beanInfos.get(eli); |
| if(bi!=null) return bi; |
| bi = new LeafBeanInfoImpl(this,eli); |
| beanInfoMap.put(bi.jaxbType,bi); |
| return bi; |
| } |
| |
| protected ClassBeanInfoImpl getOrCreate( RuntimeClassInfo ci ) { |
| ClassBeanInfoImpl bi = (ClassBeanInfoImpl)beanInfos.get(ci); |
| if(bi!=null) return bi; |
| bi = new ClassBeanInfoImpl(this,ci); |
| beanInfoMap.put(bi.jaxbType,bi); |
| return bi; |
| } |
| |
| protected JaxBeanInfo getOrCreate( RuntimeArrayInfo ai ) { |
| JaxBeanInfo abi = beanInfos.get(ai); |
| if(abi!=null) return abi; |
| |
| abi = new ArrayBeanInfoImpl(this,ai); |
| |
| beanInfoMap.put(ai.getType(),abi); |
| return abi; |
| } |
| |
| public JaxBeanInfo getOrCreate(RuntimeTypeInfo e) { |
| if(e instanceof RuntimeElementInfo) |
| return getOrCreate((RuntimeElementInfo)e); |
| if(e instanceof RuntimeClassInfo) |
| return getOrCreate((RuntimeClassInfo)e); |
| if(e instanceof RuntimeLeafInfo) { |
| JaxBeanInfo bi = beanInfos.get(e); // must have been created |
| assert bi!=null; |
| return bi; |
| } |
| if(e instanceof RuntimeArrayInfo) |
| return getOrCreate((RuntimeArrayInfo)e); |
| if(e.getType()==Object.class) { |
| // anyType |
| JaxBeanInfo bi = beanInfoMap.get(Object.class); |
| if(bi==null) { |
| bi = new AnyTypeBeanInfo(this,e); |
| beanInfoMap.put(Object.class,bi); |
| } |
| return bi; |
| } |
| |
| throw new IllegalArgumentException(); |
| } |
| |
| /** |
| * Gets the {@link JaxBeanInfo} object that can handle |
| * the given JAXB-bound object. |
| * |
| * <p> |
| * This method traverses the base classes of the given object. |
| * |
| * @return null |
| * if {@code c} isn't a JAXB-bound class and {@code fatal==false}. |
| */ |
| public final JaxBeanInfo getBeanInfo(Object o) { |
| // don't allow xs:anyType beanInfo to handle all the unbound objects |
| for( Class c=o.getClass(); c!=Object.class; c=c.getSuperclass()) { |
| JaxBeanInfo bi = beanInfoMap.get(c); |
| if(bi!=null) return bi; |
| } |
| if(o instanceof Element) |
| return beanInfoMap.get(Object.class); // return the BeanInfo for xs:anyType |
| for( Class c : o.getClass().getInterfaces()) { |
| JaxBeanInfo bi = beanInfoMap.get(c); |
| if(bi!=null) return bi; |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the {@link JaxBeanInfo} object that can handle |
| * the given JAXB-bound object. |
| * |
| * @param fatal |
| * if true, the failure to look up will throw an exception. |
| * Otherwise it will just return null. |
| */ |
| public final JaxBeanInfo getBeanInfo(Object o,boolean fatal) throws JAXBException { |
| JaxBeanInfo bi = getBeanInfo(o); |
| if(bi!=null) return bi; |
| if(fatal) { |
| if(o instanceof Document) |
| throw new JAXBException(Messages.ELEMENT_NEEDED_BUT_FOUND_DOCUMENT.format(o.getClass())); |
| throw new JAXBException(Messages.UNKNOWN_CLASS.format(o.getClass())); |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the {@link JaxBeanInfo} object that can handle |
| * the given JAXB-bound class. |
| * |
| * <p> |
| * This method doesn't look for base classes. |
| * |
| * @return null |
| * if {@code c} isn't a JAXB-bound class and {@code fatal==false}. |
| */ |
| public final <T> JaxBeanInfo<T> getBeanInfo(Class<T> clazz) { |
| return (JaxBeanInfo<T>)beanInfoMap.get(clazz); |
| } |
| |
| /** |
| * Gets the {@link JaxBeanInfo} object that can handle |
| * the given JAXB-bound class. |
| * |
| * @param fatal |
| * if true, the failure to look up will throw an exception. |
| * Otherwise it will just return null. |
| */ |
| public final <T> JaxBeanInfo<T> getBeanInfo(Class<T> clazz,boolean fatal) throws JAXBException { |
| JaxBeanInfo<T> bi = getBeanInfo(clazz); |
| if(bi!=null) return bi; |
| if(fatal) |
| throw new JAXBException(clazz.getName()+" is not known to this context"); |
| return null; |
| } |
| |
| /** |
| * Based on the tag name, determine what object to unmarshal, |
| * and then set a new object and its loader to the current unmarshaller state. |
| * |
| * @return |
| * null if the given name pair is not recognized. |
| */ |
| public final Loader selectRootLoader(UnmarshallingContext.State state, TagName tag ) { |
| JaxBeanInfo beanInfo = rootMap.get(tag.uri,tag.local); |
| if(beanInfo==null) |
| return null; |
| |
| return beanInfo.getLoader(this,true); |
| } |
| |
| /** |
| * Gets the {@link JaxBeanInfo} for the given named XML Schema type. |
| * |
| * @return |
| * null if the type name is not recognized. For schema |
| * languages other than XML Schema, this method always |
| * returns null. |
| */ |
| public JaxBeanInfo getGlobalType(QName name) { |
| return typeMap.get(name); |
| } |
| |
| /** |
| * Finds a type name that this context recognizes which is |
| * "closest" to the given type name. |
| * |
| * <p> |
| * This method is used for error recovery. |
| */ |
| public String getNearestTypeName(QName name) { |
| String[] all = new String[typeMap.size()]; |
| int i=0; |
| for (QName qn : typeMap.keySet()) { |
| if(qn.getLocalPart().equals(name.getLocalPart())) |
| return qn.toString(); // probably a match, as people often gets confused about namespace. |
| all[i++] = qn.toString(); |
| } |
| |
| String nearest = EditDistance.findNearest(name.toString(), all); |
| |
| if(EditDistance.editDistance(nearest,name.toString())>10) |
| return null; // too far apart. |
| |
| return nearest; |
| } |
| |
| /** |
| * Returns the set of valid root tag names. |
| * For diagnostic use. |
| */ |
| public Set<QName> getValidRootNames() { |
| Set<QName> r = new TreeSet<QName>(QNAME_COMPARATOR); |
| for (QNameMap.Entry e : rootMap.entrySet()) { |
| r.add(e.createQName()); |
| } |
| return r; |
| } |
| |
| /** |
| * Cache of UTF-8 encoded local names to improve the performance for the marshalling. |
| */ |
| private Encoded[] utf8nameTable; |
| |
| public synchronized Encoded[] getUTF8NameTable() { |
| if(utf8nameTable==null) { |
| Encoded[] x = new Encoded[nameList.localNames.length]; |
| for( int i=0; i<x.length; i++ ) { |
| Encoded e = new Encoded(nameList.localNames[i]); |
| e.compact(); |
| x[i] = e; |
| } |
| utf8nameTable = x; |
| } |
| return utf8nameTable; |
| } |
| |
| public int getNumberOfLocalNames() { |
| return nameList.localNames.length; |
| } |
| |
| public int getNumberOfElementNames() { |
| return nameList.numberOfElementNames; |
| } |
| |
| public int getNumberOfAttributeNames() { |
| return nameList.numberOfAttributeNames; |
| } |
| |
| /** |
| * Creates a new identity transformer. |
| */ |
| static Transformer createTransformer(boolean disableSecureProcessing) { |
| try { |
| SAXTransformerFactory tf = (SAXTransformerFactory)XmlFactory.createTransformerFactory(disableSecureProcessing); |
| return tf.newTransformer(); |
| } catch (TransformerConfigurationException e) { |
| throw new Error(e); // impossible |
| } |
| } |
| |
| /** |
| * Creates a new identity transformer. |
| */ |
| public static TransformerHandler createTransformerHandler(boolean disableSecureProcessing) { |
| try { |
| SAXTransformerFactory tf = (SAXTransformerFactory)XmlFactory.createTransformerFactory(disableSecureProcessing); |
| return tf.newTransformerHandler(); |
| } catch (TransformerConfigurationException e) { |
| throw new Error(e); // impossible |
| } |
| } |
| |
| /** |
| * Creates a new DOM document. |
| */ |
| static Document createDom(boolean disableSecurityProcessing) { |
| synchronized(JAXBContextImpl.class) { |
| if(db==null) { |
| try { |
| DocumentBuilderFactory dbf = XmlFactory.createDocumentBuilderFactory(disableSecurityProcessing); |
| db = dbf.newDocumentBuilder(); |
| } catch (ParserConfigurationException e) { |
| // impossible |
| throw new FactoryConfigurationError(e); |
| } |
| } |
| return db.newDocument(); |
| } |
| } |
| |
| public MarshallerImpl createMarshaller() { |
| return new MarshallerImpl(this,null); |
| } |
| |
| public UnmarshallerImpl createUnmarshaller() { |
| return new UnmarshallerImpl(this,null); |
| } |
| |
| @Override |
| public JAXBIntrospector createJAXBIntrospector() { |
| return new JAXBIntrospector() { |
| public boolean isElement(Object object) { |
| return getElementName(object)!=null; |
| } |
| |
| public QName getElementName(Object jaxbElement) { |
| try { |
| return JAXBContextImpl.this.getElementName(jaxbElement); |
| } catch (JAXBException e) { |
| return null; |
| } |
| } |
| }; |
| } |
| |
| private NonElement<Type,Class> getXmlType(RuntimeTypeInfoSet tis, TypeReference tr) { |
| if(tr==null) |
| throw new IllegalArgumentException(); |
| |
| XmlJavaTypeAdapter xjta = tr.get(XmlJavaTypeAdapter.class); |
| XmlList xl = tr.get(XmlList.class); |
| |
| Ref<Type,Class> ref = new Ref<Type,Class>(annotationReader, tis.getNavigator(), tr.type, xjta, xl ); |
| |
| return tis.getTypeInfo(ref); |
| } |
| |
| @Override |
| public void generateEpisode(Result output) { |
| if(output==null) |
| throw new IllegalArgumentException(); |
| createSchemaGenerator().writeEpisodeFile(ResultFactory.createSerializer(output)); |
| } |
| |
| @Override |
| @SuppressWarnings("ThrowableInitCause") |
| public void generateSchema(SchemaOutputResolver outputResolver) throws IOException { |
| if(outputResolver==null) |
| throw new IOException(Messages.NULL_OUTPUT_RESOLVER.format()); |
| |
| final SAXParseException[] e = new SAXParseException[1]; |
| final SAXParseException[] w = new SAXParseException[1]; |
| |
| createSchemaGenerator().write(outputResolver, new ErrorListener() { |
| public void error(SAXParseException exception) { |
| e[0] = exception; |
| } |
| |
| public void fatalError(SAXParseException exception) { |
| e[0] = exception; |
| } |
| |
| public void warning(SAXParseException exception) { |
| w[0] = exception; |
| } |
| |
| public void info(SAXParseException exception) {} |
| }); |
| |
| if (e[0]!=null) { |
| IOException x = new IOException(Messages.FAILED_TO_GENERATE_SCHEMA.format()); |
| x.initCause(e[0]); |
| throw x; |
| } |
| if (w[0]!=null) { |
| IOException x = new IOException(Messages.ERROR_PROCESSING_SCHEMA.format()); |
| x.initCause(w[0]); |
| throw x; |
| } |
| } |
| |
| private XmlSchemaGenerator<Type,Class,Field,Method> createSchemaGenerator() { |
| RuntimeTypeInfoSet tis; |
| try { |
| tis = getTypeInfoSet(); |
| } catch (IllegalAnnotationsException e) { |
| // this shouldn't happen because we've already |
| throw new AssertionError(e); |
| } |
| |
| XmlSchemaGenerator<Type,Class,Field,Method> xsdgen = |
| new XmlSchemaGenerator<Type,Class,Field,Method>(tis.getNavigator(),tis); |
| |
| // JAX-RPC uses Bridge objects that collide with |
| // @XmlRootElement. |
| // we will avoid collision here |
| Set<QName> rootTagNames = new HashSet<QName>(); |
| for (RuntimeElementInfo ei : tis.getAllElements()) { |
| rootTagNames.add(ei.getElementName()); |
| } |
| for (RuntimeClassInfo ci : tis.beans().values()) { |
| if(ci.isElement()) |
| rootTagNames.add(ci.asElement().getElementName()); |
| } |
| |
| for (TypeReference tr : bridges.keySet()) { |
| if(rootTagNames.contains(tr.tagName)) |
| continue; |
| |
| if(tr.type==void.class || tr.type==Void.class) { |
| xsdgen.add(tr.tagName,false,null); |
| } else |
| if(tr.type==CompositeStructure.class) { |
| // this is a special class we introduced for JAX-WS that we *don't* want in the schema |
| } else { |
| NonElement<Type,Class> typeInfo = getXmlType(tis,tr); |
| xsdgen.add(tr.tagName, !tis.getNavigator().isPrimitive(tr.type),typeInfo); |
| } |
| } |
| return xsdgen; |
| } |
| |
| public QName getTypeName(TypeReference tr) { |
| try { |
| NonElement<Type,Class> xt = getXmlType(getTypeInfoSet(),tr); |
| if(xt==null) throw new IllegalArgumentException(); |
| return xt.getTypeName(); |
| } catch (IllegalAnnotationsException e) { |
| // impossible given that JAXBRIContext has been successfully built in the first place |
| throw new AssertionError(e); |
| } |
| } |
| |
| @Override |
| public <T> Binder<T> createBinder(Class<T> domType) { |
| if(domType==Node.class) |
| return (Binder<T>)createBinder(); |
| else |
| return super.createBinder(domType); |
| } |
| |
| @Override |
| public Binder<Node> createBinder() { |
| return new BinderImpl<Node>(this,new DOMScanner()); |
| } |
| |
| public QName getElementName(Object o) throws JAXBException { |
| JaxBeanInfo bi = getBeanInfo(o,true); |
| if(!bi.isElement()) |
| return null; |
| return new QName(bi.getElementNamespaceURI(o),bi.getElementLocalName(o)); |
| } |
| |
| public QName getElementName(Class o) throws JAXBException { |
| JaxBeanInfo bi = getBeanInfo(o,true); |
| if(!bi.isElement()) |
| return null; |
| return new QName(bi.getElementNamespaceURI(o),bi.getElementLocalName(o)); |
| } |
| |
| public Bridge createBridge(TypeReference ref) { |
| return bridges.get(ref); |
| } |
| |
| public @NotNull BridgeContext createBridgeContext() { |
| return new BridgeContextImpl(this); |
| } |
| |
| public RawAccessor getElementPropertyAccessor(Class wrapperBean, String nsUri, String localName) throws JAXBException { |
| JaxBeanInfo bi = getBeanInfo(wrapperBean,true); |
| if(!(bi instanceof ClassBeanInfoImpl)) |
| throw new JAXBException(wrapperBean+" is not a bean"); |
| |
| for( ClassBeanInfoImpl cb = (ClassBeanInfoImpl) bi; cb!=null; cb=cb.superClazz) { |
| for (Property p : cb.properties) { |
| final Accessor acc = p.getElementPropertyAccessor(nsUri,localName); |
| if(acc!=null) |
| return new RawAccessor() { |
| // Accessor.set/get are designed for unmarshaller/marshaller, and hence |
| // they go through an adapter behind the scene. |
| // this isn't desirable for JAX-WS, which essentially uses this method |
| // just as a reflection library. So use the "unadapted" version to |
| // achieve the desired semantics |
| public Object get(Object bean) throws AccessorException { |
| return acc.getUnadapted(bean); |
| } |
| |
| public void set(Object bean, Object value) throws AccessorException { |
| acc.setUnadapted(bean,value); |
| } |
| }; |
| } |
| } |
| throw new JAXBException(new QName(nsUri,localName)+" is not a valid property on "+wrapperBean); |
| } |
| |
| public List<String> getKnownNamespaceURIs() { |
| return Arrays.asList(nameList.namespaceURIs); |
| } |
| |
| public String getBuildId() { |
| Package pkg = getClass().getPackage(); |
| if(pkg==null) return null; |
| return pkg.getImplementationVersion(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buf = new StringBuilder(Which.which(getClass()) + " Build-Id: " + getBuildId()); |
| buf.append("\nClasses known to this context:\n"); |
| |
| Set<String> names = new TreeSet<String>(); // sort them so that it's easy to read |
| |
| for (Class key : beanInfoMap.keySet()) |
| names.add(key.getName()); |
| |
| for(String name: names) |
| buf.append(" ").append(name).append('\n'); |
| |
| return buf.toString(); |
| } |
| |
| /** |
| * Gets the value of the xmime:contentType attribute on the given object, or null |
| * if for some reason it couldn't be found, including any error. |
| */ |
| public String getXMIMEContentType( Object o ) { |
| JaxBeanInfo bi = getBeanInfo(o); |
| if(!(bi instanceof ClassBeanInfoImpl)) |
| return null; |
| |
| ClassBeanInfoImpl cb = (ClassBeanInfoImpl) bi; |
| for (Property p : cb.properties) { |
| if (p instanceof AttributeProperty) { |
| AttributeProperty ap = (AttributeProperty) p; |
| if(ap.attName.equals(WellKnownNamespace.XML_MIME_URI,"contentType")) |
| try { |
| return (String)ap.xacc.print(o); |
| } catch (AccessorException e) { |
| return null; |
| } catch (SAXException e) { |
| return null; |
| } catch (ClassCastException e) { |
| return null; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a {@link JAXBContextImpl} that includes the specified additional classes. |
| */ |
| public JAXBContextImpl createAugmented(Class<?> clazz) throws JAXBException { |
| Class[] newList = new Class[classes.length+1]; |
| System.arraycopy(classes,0,newList,0,classes.length); |
| newList[classes.length] = clazz; |
| |
| JAXBContextBuilder builder = new JAXBContextBuilder(this); |
| builder.setClasses(newList); |
| return builder.build(); |
| } |
| |
| private static final Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() { |
| public int compare(QName lhs, QName rhs) { |
| int r = lhs.getLocalPart().compareTo(rhs.getLocalPart()); |
| if(r!=0) return r; |
| |
| return lhs.getNamespaceURI().compareTo(rhs.getNamespaceURI()); |
| } |
| }; |
| |
| public static class JAXBContextBuilder { |
| |
| private boolean retainPropertyInfo = false; |
| private boolean supressAccessorWarnings = false; |
| private String defaultNsUri = ""; |
| private @NotNull RuntimeAnnotationReader annotationReader = new RuntimeInlineAnnotationReader(); |
| private @NotNull Map<Class,Class> subclassReplacements = Collections.emptyMap(); |
| private boolean c14nSupport = false; |
| private Class[] classes; |
| private Collection<TypeReference> typeRefs; |
| private boolean xmlAccessorFactorySupport = false; |
| private boolean allNillable; |
| private boolean improvedXsiTypeHandling = true; |
| private boolean disableSecurityProcessing = true; |
| private Boolean backupWithParentNamespace = null; // null for System property to be used |
| private int maxErrorsCount; |
| |
| public JAXBContextBuilder() {}; |
| |
| public JAXBContextBuilder(JAXBContextImpl baseImpl) { |
| this.supressAccessorWarnings = baseImpl.supressAccessorWarnings; |
| this.retainPropertyInfo = baseImpl.retainPropertyInfo; |
| this.defaultNsUri = baseImpl.defaultNsUri; |
| this.annotationReader = baseImpl.annotationReader; |
| this.subclassReplacements = baseImpl.subclassReplacements; |
| this.c14nSupport = baseImpl.c14nSupport; |
| this.classes = baseImpl.classes; |
| this.typeRefs = baseImpl.bridges.keySet(); |
| this.xmlAccessorFactorySupport = baseImpl.xmlAccessorFactorySupport; |
| this.allNillable = baseImpl.allNillable; |
| this.disableSecurityProcessing = baseImpl.disableSecurityProcessing; |
| this.backupWithParentNamespace = baseImpl.backupWithParentNamespace; |
| this.maxErrorsCount = baseImpl.maxErrorsCount; |
| } |
| |
| public JAXBContextBuilder setRetainPropertyInfo(boolean val) { |
| this.retainPropertyInfo = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setSupressAccessorWarnings(boolean val) { |
| this.supressAccessorWarnings = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setC14NSupport(boolean val) { |
| this.c14nSupport = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setXmlAccessorFactorySupport(boolean val) { |
| this.xmlAccessorFactorySupport = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setDefaultNsUri(String val) { |
| this.defaultNsUri = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setAllNillable(boolean val) { |
| this.allNillable = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setClasses(Class[] val) { |
| this.classes = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setAnnotationReader(RuntimeAnnotationReader val) { |
| this.annotationReader = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setSubclassReplacements(Map<Class,Class> val) { |
| this.subclassReplacements = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setTypeRefs(Collection<TypeReference> val) { |
| this.typeRefs = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setImprovedXsiTypeHandling(boolean val) { |
| this.improvedXsiTypeHandling = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setDisableSecurityProcessing(boolean val) { |
| this.disableSecurityProcessing = val; |
| return this; |
| } |
| |
| public JAXBContextBuilder setBackupWithParentNamespace(Boolean backupWithParentNamespace) { |
| this.backupWithParentNamespace = backupWithParentNamespace; |
| return this; |
| } |
| |
| public JAXBContextBuilder setMaxErrorsCount(int maxErrorsCount) { |
| this.maxErrorsCount = maxErrorsCount; |
| return this; |
| } |
| |
| public JAXBContextImpl build() throws JAXBException { |
| |
| // fool-proof |
| if (this.defaultNsUri == null) { |
| this.defaultNsUri = ""; |
| } |
| |
| if (this.subclassReplacements == null) { |
| this.subclassReplacements = Collections.emptyMap(); |
| } |
| |
| if (this.annotationReader == null) { |
| this.annotationReader = new RuntimeInlineAnnotationReader(); |
| } |
| |
| if (this.typeRefs == null) { |
| this.typeRefs = Collections.<TypeReference>emptyList(); |
| } |
| |
| return new JAXBContextImpl(this); |
| } |
| |
| } |
| |
| } |