| /* |
| * 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.model.impl; |
| |
| import org.glassfish.jaxb.core.WhiteSpaceProcessor; |
| import org.glassfish.jaxb.core.util.Which; |
| import org.glassfish.jaxb.core.v2.model.annotation.AnnotationReader; |
| import org.glassfish.jaxb.runtime.v2.model.annotation.ClassLocatable; |
| import org.glassfish.jaxb.core.v2.model.annotation.Locatable; |
| import org.glassfish.jaxb.core.v2.model.core.*; |
| import org.glassfish.jaxb.core.v2.model.impl.ModelBuilderI; |
| import org.glassfish.jaxb.core.v2.model.nav.Navigator; |
| import org.glassfish.jaxb.runtime.v2.model.runtime.RuntimePropertyInfo; |
| import org.glassfish.jaxb.core.v2.runtime.IllegalAnnotationException; |
| import jakarta.xml.bind.JAXBElement; |
| import jakarta.xml.bind.annotation.*; |
| |
| import javax.xml.namespace.QName; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Builds a {@link TypeInfoSet} (a set of JAXB properties) |
| * by using {@link ElementInfoImpl} and {@link ClassInfoImpl}. |
| * from annotated Java classes. |
| * |
| * <p> |
| * This class uses {@link Navigator} and {@link AnnotationReader} to |
| * work with arbitrary annotation source and arbitrary Java model. |
| * For this purpose this class is parameterized. |
| * |
| * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) |
| */ |
| public class ModelBuilder<T,C,F,M> implements ModelBuilderI<T,C,F,M> { |
| private static final Logger logger; |
| |
| /** |
| * {@link TypeInfo}s that are built will go into this set. |
| */ |
| final TypeInfoSetImpl<T,C,F,M> typeInfoSet; |
| |
| public final AnnotationReader<T,C,F,M> reader; |
| |
| public final Navigator<T,C,F,M> nav; |
| |
| /** |
| * Used to detect collisions among global type names. |
| */ |
| private final Map<QName,TypeInfo> typeNames = new HashMap<>(); |
| |
| /** |
| * JAXB doesn't want to use namespaces unless we are told to, but WS-I BP |
| * conformace requires JAX-RPC to always use a non-empty namespace URI. |
| * (see http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html#WSDLTYPES R2105) |
| * |
| * <p> |
| * To work around this issue, we allow the use of the empty namespaces to be |
| * replaced by a particular designated namespace URI. |
| * |
| * <p> |
| * This field keeps the value of that replacing namespace URI. |
| * When there's no replacement, this field is set to "". |
| */ |
| public final String defaultNsUri; |
| |
| |
| /** |
| * Packages whose registries are already added. |
| */ |
| /*package*/ final Map<String,RegistryInfoImpl<T,C,F,M>> registries |
| = new HashMap<>(); |
| |
| private final Map<C,C> subclassReplacements; |
| |
| /** |
| * @see #setErrorHandler |
| */ |
| private ErrorHandler errorHandler; |
| private boolean hadError; |
| |
| /** |
| * Set to true if the model includes {@link XmlAttachmentRef}. JAX-WS |
| * needs to know this information. |
| */ |
| public boolean hasSwaRef; |
| |
| private final ErrorHandler proxyErrorHandler = new ErrorHandler() { |
| @Override |
| public void error(IllegalAnnotationException e) { |
| reportError(e); |
| } |
| }; |
| |
| public ModelBuilder( |
| AnnotationReader<T, C, F, M> reader, |
| Navigator<T, C, F, M> navigator, |
| Map<C, C> subclassReplacements, |
| String defaultNamespaceRemap |
| ) { |
| |
| this.reader = reader; |
| this.nav = navigator; |
| this.subclassReplacements = subclassReplacements; |
| if(defaultNamespaceRemap==null) |
| defaultNamespaceRemap = ""; |
| this.defaultNsUri = defaultNamespaceRemap; |
| reader.setErrorHandler(proxyErrorHandler); |
| typeInfoSet = createTypeInfoSet(); |
| } |
| |
| /** |
| * Makes sure that we are running with 2.1 JAXB API, |
| * and report an error if not. |
| */ |
| static { |
| try { |
| XmlSchema s = null; |
| s.location(); |
| } catch (NullPointerException e) { |
| // as epxected |
| } catch (NoSuchMethodError e) { |
| // this is not a 2.1 API. Where is it being loaded from? |
| Messages res; |
| if (SecureLoader.getClassClassLoader(XmlSchema.class) == null) { |
| res = Messages.INCOMPATIBLE_API_VERSION_MUSTANG; |
| } else { |
| res = Messages.INCOMPATIBLE_API_VERSION; |
| } |
| |
| throw new LinkageError( res.format( |
| Which.which(XmlSchema.class), |
| Which.which(ModelBuilder.class) |
| )); |
| } |
| } |
| |
| /** |
| * Makes sure that we don't have conflicting 1.0 runtime, |
| * and report an error if we do. |
| */ |
| static { |
| try { |
| WhiteSpaceProcessor.isWhiteSpace("xyz"); |
| } catch (NoSuchMethodError e) { |
| // we seem to be getting 1.0 runtime |
| throw new LinkageError( Messages.RUNNING_WITH_1_0_RUNTIME.format( |
| Which.which(WhiteSpaceProcessor.class), |
| Which.which(ModelBuilder.class) |
| )); |
| } |
| } |
| |
| /** |
| * Logger init |
| */ |
| static { |
| logger = Logger.getLogger(ModelBuilder.class.getName()); |
| } |
| |
| protected TypeInfoSetImpl<T,C,F,M> createTypeInfoSet() { |
| return new TypeInfoSetImpl<>(nav,reader,BuiltinLeafInfoImpl.createLeaves(nav)); |
| } |
| |
| /** |
| * Builds a JAXB {@link ClassInfo} model from a given class declaration |
| * and adds that to this model owner. |
| * |
| * <p> |
| * Return type is either {@link ClassInfo} or {@link LeafInfo} (for types like |
| * {@link String} or {@link Enum}-derived ones) |
| */ |
| public NonElement<T,C> getClassInfo( C clazz, Locatable upstream ) { |
| return getClassInfo(clazz,false,upstream); |
| } |
| |
| /** |
| * For limited cases where the caller needs to search for a super class. |
| * This is necessary because we don't want {@link #subclassReplacements} |
| * to kick in for the super class search, which will cause infinite recursion. |
| */ |
| public NonElement<T,C> getClassInfo( C clazz, boolean searchForSuperClass, Locatable upstream ) { |
| assert clazz!=null; |
| NonElement<T,C> r = typeInfoSet.getClassInfo(clazz); |
| if(r!=null) |
| return r; |
| |
| if(nav.isEnum(clazz)) { |
| EnumLeafInfoImpl<T,C,F,M> li = createEnumLeafInfo(clazz,upstream); |
| typeInfoSet.add(li); |
| r = li; |
| addTypeName(r); |
| } else { |
| boolean isReplaced = subclassReplacements.containsKey(clazz); |
| if(isReplaced && !searchForSuperClass) { |
| // handle it as if the replacement was specified |
| r = getClassInfo(subclassReplacements.get(clazz),upstream); |
| } else |
| if(reader.hasClassAnnotation(clazz,XmlTransient.class) || isReplaced) { |
| // handle it as if the base class was specified |
| r = getClassInfo( nav.getSuperClass(clazz), searchForSuperClass, |
| new ClassLocatable<>(upstream,clazz,nav) ); |
| } else { |
| ClassInfoImpl<T,C,F,M> ci = createClassInfo(clazz,upstream); |
| typeInfoSet.add(ci); |
| |
| // compute the closure by eagerly expanding references |
| for( PropertyInfo<T,C> p : ci.getProperties() ) { |
| if(p.kind()== PropertyKind.REFERENCE) { |
| // make sure that we have a registry for this package |
| addToRegistry(clazz, (Locatable) p); |
| Class[] prmzdClasses = getParametrizedTypes(p); |
| if (prmzdClasses != null) { |
| for (Class prmzdClass : prmzdClasses) { |
| if (prmzdClass != clazz) { |
| addToRegistry((C) prmzdClass, (Locatable) p); |
| } |
| } |
| } |
| } |
| |
| for( TypeInfo<T,C> t : p.ref() ) |
| ; // just compute a reference should be suffice |
| } |
| ci.getBaseClass(); // same as above. |
| |
| r = ci; |
| addTypeName(r); |
| } |
| } |
| |
| |
| // more reference closure expansion. @XmlSeeAlso |
| XmlSeeAlso sa = reader.getClassAnnotation(XmlSeeAlso.class, clazz, upstream); |
| if(sa!=null) { |
| for( T t : reader.getClassArrayValue(sa,"value") ) { |
| getTypeInfo(t,(Locatable)sa); |
| } |
| } |
| |
| |
| return r; |
| } |
| |
| /** |
| * Adding package's ObjectFactory methods to registry |
| * @param clazz which package will be used |
| * @param p location |
| */ |
| private void addToRegistry(C clazz, Locatable p) { |
| String pkg = nav.getPackageName(clazz); |
| if (!registries.containsKey(pkg)) { |
| // insert the package's object factory |
| C c = nav.loadObjectFactory(clazz, pkg); |
| if (c != null) |
| addRegistry(c, p); |
| } |
| } |
| |
| /** |
| * Getting parametrized classes of {@code JAXBElement<...>} property |
| * @param p property which parametrized types we will try to get |
| * @return null - if it's not JAXBElement property, or it's not parametrized, and array of parametrized classes in other case |
| */ |
| private Class[] getParametrizedTypes(PropertyInfo p) { |
| try { |
| Type pType = ((RuntimePropertyInfo) p).getIndividualType(); |
| if (pType instanceof ParameterizedType) { |
| ParameterizedType prmzdType = (ParameterizedType) pType; |
| if (prmzdType.getRawType() == JAXBElement.class) { |
| Type[] actualTypes = prmzdType.getActualTypeArguments(); |
| Class[] result = new Class[actualTypes.length]; |
| for (int i = 0; i < actualTypes.length; i++) { |
| result[i] = (Class) actualTypes[i]; |
| } |
| return result; |
| } |
| } |
| } catch (Exception e) { |
| logger.log(Level.FINE, "Error in ModelBuilder.getParametrizedTypes. " + e.getMessage()); |
| } |
| return null; |
| } |
| |
| /** |
| * Checks the uniqueness of the type name. |
| */ |
| private void addTypeName(NonElement<T, C> r) { |
| QName t = r.getTypeName(); |
| if(t==null) return; |
| |
| TypeInfo old = typeNames.put(t,r); |
| if(old!=null) { |
| // collision |
| reportError(new IllegalAnnotationException( |
| Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()), |
| old, r )); |
| } |
| } |
| |
| /** |
| * Have the builder recognize the type (if it hasn't done so yet), |
| * and returns a {@link NonElement} that represents it. |
| * |
| * @return |
| * always non-null. |
| */ |
| public NonElement<T,C> getTypeInfo(T t,Locatable upstream) { |
| NonElement<T,C> r = typeInfoSet.getTypeInfo(t); |
| if(r!=null) return r; |
| |
| if(nav.isArray(t)) { // no need for checking byte[], because above typeInfoset.getTypeInfo() would return non-null |
| ArrayInfoImpl<T,C,F,M> ai = |
| createArrayInfo(upstream, t); |
| addTypeName(ai); |
| typeInfoSet.add(ai); |
| return ai; |
| } |
| |
| C c = nav.asDecl(t); |
| assert c!=null : t.toString()+" must be a leaf, but we failed to recognize it."; |
| return getClassInfo(c,upstream); |
| } |
| |
| /** |
| * This method is used to add a root reference to a model. |
| */ |
| public NonElement<T,C> getTypeInfo(Ref<T,C> ref) { |
| // TODO: handle XmlValueList |
| assert !ref.valueList; |
| C c = nav.asDecl(ref.type); |
| if(c!=null && reader.getClassAnnotation(XmlRegistry.class,c,null/*TODO: is this right?*/)!=null) { |
| if(!registries.containsKey(nav.getPackageName(c))) |
| addRegistry(c,null); |
| return null; // TODO: is this correct? |
| } else |
| return getTypeInfo(ref.type,null); |
| } |
| |
| |
| protected EnumLeafInfoImpl<T,C,F,M> createEnumLeafInfo(C clazz,Locatable upstream) { |
| return new EnumLeafInfoImpl<>(this,upstream,clazz,nav.use(clazz)); |
| } |
| |
| protected ClassInfoImpl<T,C,F,M> createClassInfo(C clazz, Locatable upstream ) { |
| return new ClassInfoImpl<>(this,upstream,clazz); |
| } |
| |
| protected ElementInfoImpl<T,C,F,M> createElementInfo( |
| RegistryInfoImpl<T,C,F,M> registryInfo, M m) throws IllegalAnnotationException { |
| return new ElementInfoImpl<>(this,registryInfo,m); |
| } |
| |
| protected ArrayInfoImpl<T,C,F,M> createArrayInfo(Locatable upstream, T arrayType) { |
| return new ArrayInfoImpl<>(this,upstream,arrayType); |
| } |
| |
| |
| /** |
| * Visits a class with {@link XmlRegistry} and records all the element mappings |
| * in it. |
| */ |
| public RegistryInfo<T,C> addRegistry(C registryClass, Locatable upstream ) { |
| return new RegistryInfoImpl<>(this,upstream,registryClass); |
| } |
| |
| /** |
| * Gets a {@link RegistryInfo} for the given package. |
| * |
| * @return |
| * null if no registry exists for the package. |
| * unlike other getXXX methods on this class, |
| * this method is side-effect free. |
| */ |
| public RegistryInfo<T,C> getRegistry(String packageName) { |
| return registries.get(packageName); |
| } |
| |
| private boolean linked; |
| |
| /** |
| * Called after all the classes are added to the type set |
| * to "link" them together. |
| * |
| * <p> |
| * Don't expose implementation classes in the signature. |
| * |
| * @return |
| * fully built {@link TypeInfoSet} that represents the model, |
| * or null if there was an error. |
| */ |
| public TypeInfoSet<T,C,F,M> link() { |
| |
| assert !linked; |
| linked = true; |
| |
| for( ElementInfoImpl ei : typeInfoSet.getAllElements() ) |
| ei.link(); |
| |
| for( ClassInfoImpl ci : typeInfoSet.beans().values() ) |
| ci.link(); |
| |
| for( EnumLeafInfoImpl li : typeInfoSet.enums().values() ) |
| li.link(); |
| |
| if(hadError) |
| return null; |
| else |
| return typeInfoSet; |
| } |
| |
| // |
| // |
| // error handling |
| // |
| // |
| |
| /** |
| * Sets the error handler that receives errors discovered during the model building. |
| * |
| * @param errorHandler |
| * can be null. |
| */ |
| public void setErrorHandler(ErrorHandler errorHandler) { |
| this.errorHandler = errorHandler; |
| } |
| |
| public final void reportError(IllegalAnnotationException e) { |
| hadError = true; |
| if(errorHandler!=null) |
| errorHandler.error(e); |
| } |
| |
| public boolean isReplaced(C sc) { |
| return subclassReplacements.containsKey(sc); |
| } |
| |
| @Override |
| public Navigator<T, C, F, M> getNavigator() { |
| return nav; |
| } |
| |
| @Override |
| public AnnotationReader<T, C, F, M> getReader() { |
| return reader; |
| } |
| } |