1 /*
   2  * Copyright (c) 1997, 2012, 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 
  26 package com.sun.xml.internal.bind.v2.model.impl;
  27 
  28 import java.lang.reflect.ParameterizedType;
  29 import java.lang.reflect.Type;
  30 import java.util.HashMap;
  31 import java.util.Map;
  32 import java.util.logging.Level;
  33 import java.util.logging.Logger;
  34 
  35 import javax.xml.bind.JAXBElement;
  36 import javax.xml.bind.annotation.XmlAttachmentRef;
  37 import javax.xml.bind.annotation.XmlRegistry;
  38 import javax.xml.bind.annotation.XmlSchema;
  39 import javax.xml.bind.annotation.XmlSeeAlso;
  40 import javax.xml.bind.annotation.XmlTransient;
  41 import javax.xml.namespace.QName;
  42 
  43 import com.sun.xml.internal.bind.util.Which;
  44 import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
  45 import com.sun.xml.internal.bind.v2.model.annotation.ClassLocatable;
  46 import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
  47 import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
  48 import com.sun.xml.internal.bind.v2.model.core.ErrorHandler;
  49 import com.sun.xml.internal.bind.v2.model.core.LeafInfo;
  50 import com.sun.xml.internal.bind.v2.model.core.NonElement;
  51 import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
  52 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
  53 import com.sun.xml.internal.bind.v2.model.core.Ref;
  54 import com.sun.xml.internal.bind.v2.model.core.RegistryInfo;
  55 import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
  56 import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
  57 import com.sun.xml.internal.bind.v2.model.nav.Navigator;
  58 import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
  59 import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
  60 import com.sun.xml.internal.bind.WhiteSpaceProcessor;
  61 
  62 /**
  63  * Builds a {@link TypeInfoSet} (a set of JAXB properties)
  64  * by using {@link ElementInfoImpl} and {@link ClassInfoImpl}.
  65  * from annotated Java classes.
  66  *
  67  * <p>
  68  * This class uses {@link Navigator} and {@link AnnotationReader} to
  69  * work with arbitrary annotation source and arbitrary Java model.
  70  * For this purpose this class is parameterized.
  71  *
  72  * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  73  */
  74 public class ModelBuilder<T,C,F,M> implements ModelBuilderI<T,C,F,M> {
  75     private static final Logger logger;
  76 
  77     /**
  78      * {@link TypeInfo}s that are built will go into this set.
  79      */
  80     final TypeInfoSetImpl<T,C,F,M> typeInfoSet;
  81 
  82     public final AnnotationReader<T,C,F,M> reader;
  83 
  84     public final Navigator<T,C,F,M> nav;
  85 
  86     /**
  87      * Used to detect collisions among global type names.
  88      */
  89     private final Map<QName,TypeInfo> typeNames = new HashMap<QName,TypeInfo>();
  90 
  91     /**
  92      * JAXB doesn't want to use namespaces unless we are told to, but WS-I BP
  93      * conformace requires JAX-RPC to always use a non-empty namespace URI.
  94      * (see http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html#WSDLTYPES R2105)
  95      *
  96      * <p>
  97      * To work around this issue, we allow the use of the empty namespaces to be
  98      * replaced by a particular designated namespace URI.
  99      *
 100      * <p>
 101      * This field keeps the value of that replacing namespace URI.
 102      * When there's no replacement, this field is set to "".
 103      */
 104     public final String defaultNsUri;
 105 
 106 
 107     /**
 108      * Packages whose registries are already added.
 109      */
 110     /*package*/ final Map<String,RegistryInfoImpl<T,C,F,M>> registries
 111             = new HashMap<String,RegistryInfoImpl<T,C,F,M>>();
 112 
 113     private final Map<C,C> subclassReplacements;
 114 
 115     /**
 116      * @see #setErrorHandler
 117      */
 118     private ErrorHandler errorHandler;
 119     private boolean hadError;
 120 
 121     /**
 122      * Set to true if the model includes {@link XmlAttachmentRef}. JAX-WS
 123      * needs to know this information.
 124      */
 125     public boolean hasSwaRef;
 126 
 127     private final ErrorHandler proxyErrorHandler = new ErrorHandler() {
 128         public void error(IllegalAnnotationException e) {
 129             reportError(e);
 130         }
 131     };
 132 
 133     public ModelBuilder(
 134             AnnotationReader<T, C, F, M> reader,
 135             Navigator<T, C, F, M> navigator,
 136             Map<C, C> subclassReplacements,
 137             String defaultNamespaceRemap
 138     ) {
 139 
 140         this.reader = reader;
 141         this.nav = navigator;
 142         this.subclassReplacements = subclassReplacements;
 143         if(defaultNamespaceRemap==null)
 144             defaultNamespaceRemap = "";
 145         this.defaultNsUri = defaultNamespaceRemap;
 146         reader.setErrorHandler(proxyErrorHandler);
 147         typeInfoSet = createTypeInfoSet();
 148     }
 149 
 150     /**
 151      * Makes sure that we are running with 2.1 JAXB API,
 152      * and report an error if not.
 153      */
 154     static {
 155         try {
 156             XmlSchema s = null;
 157             s.location();
 158         } catch (NullPointerException e) {
 159             // as epxected
 160         } catch (NoSuchMethodError e) {
 161             // this is not a 2.1 API. Where is it being loaded from?
 162             Messages res;
 163             if (SecureLoader.getClassClassLoader(XmlSchema.class) == null) {
 164                 res = Messages.INCOMPATIBLE_API_VERSION_MUSTANG;
 165             } else {
 166                 res = Messages.INCOMPATIBLE_API_VERSION;
 167             }
 168 
 169             throw new LinkageError( res.format(
 170                 Which.which(XmlSchema.class),
 171                 Which.which(ModelBuilder.class)
 172             ));
 173         }
 174     }
 175 
 176     /**
 177      * Makes sure that we don't have conflicting 1.0 runtime,
 178      * and report an error if we do.
 179      */
 180     static {
 181         try {
 182             WhiteSpaceProcessor.isWhiteSpace("xyz");
 183         } catch (NoSuchMethodError e) {
 184             // we seem to be getting 1.0 runtime
 185             throw new LinkageError( Messages.RUNNING_WITH_1_0_RUNTIME.format(
 186                 Which.which(WhiteSpaceProcessor.class),
 187                 Which.which(ModelBuilder.class)
 188             ));
 189         }
 190     }
 191 
 192     /**
 193      * Logger init
 194      */
 195     static {
 196         logger = Logger.getLogger(ModelBuilder.class.getName());
 197     }
 198 
 199     protected TypeInfoSetImpl<T,C,F,M> createTypeInfoSet() {
 200         return new TypeInfoSetImpl<T,C,F,M>(nav,reader,BuiltinLeafInfoImpl.createLeaves(nav));
 201     }
 202 
 203     /**
 204      * Builds a JAXB {@link ClassInfo} model from a given class declaration
 205      * and adds that to this model owner.
 206      *
 207      * <p>
 208      * Return type is either {@link ClassInfo} or {@link LeafInfo} (for types like
 209      * {@link String} or {@link Enum}-derived ones)
 210      */
 211     public NonElement<T,C> getClassInfo( C clazz, Locatable upstream ) {
 212         return getClassInfo(clazz,false,upstream);
 213     }
 214 
 215     /**
 216      * For limited cases where the caller needs to search for a super class.
 217      * This is necessary because we don't want {@link #subclassReplacements}
 218      * to kick in for the super class search, which will cause infinite recursion.
 219      */
 220     public NonElement<T,C> getClassInfo( C clazz, boolean searchForSuperClass, Locatable upstream ) {
 221         assert clazz!=null;
 222         NonElement<T,C> r = typeInfoSet.getClassInfo(clazz);
 223         if(r!=null)
 224             return r;
 225 
 226         if(nav.isEnum(clazz)) {
 227             EnumLeafInfoImpl<T,C,F,M> li = createEnumLeafInfo(clazz,upstream);
 228             typeInfoSet.add(li);
 229             r = li;
 230             addTypeName(r);
 231         } else {
 232             boolean isReplaced = subclassReplacements.containsKey(clazz);
 233             if(isReplaced && !searchForSuperClass) {
 234                 // handle it as if the replacement was specified
 235                 r = getClassInfo(subclassReplacements.get(clazz),upstream);
 236             } else
 237             if(reader.hasClassAnnotation(clazz,XmlTransient.class) || isReplaced) {
 238                 // handle it as if the base class was specified
 239                 r = getClassInfo( nav.getSuperClass(clazz), searchForSuperClass,
 240                         new ClassLocatable<C>(upstream,clazz,nav) );
 241             } else {
 242                 ClassInfoImpl<T,C,F,M> ci = createClassInfo(clazz,upstream);
 243                 typeInfoSet.add(ci);
 244 
 245                 // compute the closure by eagerly expanding references
 246                 for( PropertyInfo<T,C> p : ci.getProperties() ) {
 247                     if(p.kind()== PropertyKind.REFERENCE) {
 248                         // make sure that we have a registry for this package
 249                         addToRegistry(clazz, (Locatable) p);
 250                         Class[] prmzdClasses = getParametrizedTypes(p);
 251                         if (prmzdClasses != null) {
 252                             for (Class prmzdClass : prmzdClasses) {
 253                                 if (prmzdClass != clazz) {
 254                                     addToRegistry((C) prmzdClass, (Locatable) p);
 255                                 }
 256                             }
 257                         }
 258                     }
 259 
 260                     for( TypeInfo<T,C> t : p.ref() )
 261                         ; // just compute a reference should be suffice
 262                 }
 263                 ci.getBaseClass(); // same as above.
 264 
 265                 r = ci;
 266                 addTypeName(r);
 267             }
 268         }
 269 
 270 
 271         // more reference closure expansion. @XmlSeeAlso
 272         XmlSeeAlso sa = reader.getClassAnnotation(XmlSeeAlso.class, clazz, upstream);
 273         if(sa!=null) {
 274             for( T t : reader.getClassArrayValue(sa,"value") ) {
 275                 getTypeInfo(t,(Locatable)sa);
 276             }
 277         }
 278 
 279 
 280         return r;
 281     }
 282 
 283     /**
 284      * Adding package's ObjectFactory methods to registry
 285      * @param clazz which package will be used
 286      * @param p location
 287      */
 288     private void addToRegistry(C clazz, Locatable p) {
 289         String pkg = nav.getPackageName(clazz);
 290         if (!registries.containsKey(pkg)) {
 291             // insert the package's object factory
 292             C c = nav.findClass(pkg + ".ObjectFactory", clazz);
 293             if (c != null)
 294                 addRegistry(c, p);
 295         }
 296     }
 297 
 298     /**
 299      * Getting parametrized classes of {@code JAXBElement<...>} property
 300      * @param p property which parametrized types we will try to get
 301      * @return null - if it's not JAXBElement property, or it's not parametrized, and array of parametrized classes in other case
 302      */
 303     private Class[] getParametrizedTypes(PropertyInfo p) {
 304         try {
 305             Type pType = ((RuntimePropertyInfo) p).getIndividualType();
 306             if (pType instanceof ParameterizedType) {
 307                 ParameterizedType prmzdType = (ParameterizedType) pType;
 308                 if (prmzdType.getRawType() == JAXBElement.class) {
 309                     Type[] actualTypes = prmzdType.getActualTypeArguments();
 310                     Class[] result = new Class[actualTypes.length];
 311                     for (int i = 0; i < actualTypes.length; i++) {
 312                         result[i] = (Class) actualTypes[i];
 313                     }
 314                     return result;
 315                 }
 316             }
 317         } catch (Exception e) {
 318             logger.log(Level.FINE, "Error in ModelBuilder.getParametrizedTypes. " + e.getMessage());
 319         }
 320         return null;
 321     }
 322 
 323     /**
 324      * Checks the uniqueness of the type name.
 325      */
 326     private void addTypeName(NonElement<T, C> r) {
 327         QName t = r.getTypeName();
 328         if(t==null)     return;
 329 
 330         TypeInfo old = typeNames.put(t,r);
 331         if(old!=null) {
 332             // collision
 333             reportError(new IllegalAnnotationException(
 334                     Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()),
 335                     old, r ));
 336         }
 337     }
 338 
 339     /**
 340      * Have the builder recognize the type (if it hasn't done so yet),
 341      * and returns a {@link NonElement} that represents it.
 342      *
 343      * @return
 344      *      always non-null.
 345      */
 346     public NonElement<T,C> getTypeInfo(T t,Locatable upstream) {
 347         NonElement<T,C> r = typeInfoSet.getTypeInfo(t);
 348         if(r!=null)     return r;
 349 
 350         if(nav.isArray(t)) { // no need for checking byte[], because above typeInfoset.getTypeInfo() would return non-null
 351             ArrayInfoImpl<T,C,F,M> ai =
 352                 createArrayInfo(upstream, t);
 353             addTypeName(ai);
 354             typeInfoSet.add(ai);
 355             return ai;
 356         }
 357 
 358         C c = nav.asDecl(t);
 359         assert c!=null : t.toString()+" must be a leaf, but we failed to recognize it.";
 360         return getClassInfo(c,upstream);
 361     }
 362 
 363     /**
 364      * This method is used to add a root reference to a model.
 365      */
 366     public NonElement<T,C> getTypeInfo(Ref<T,C> ref) {
 367         // TODO: handle XmlValueList
 368         assert !ref.valueList;
 369         C c = nav.asDecl(ref.type);
 370         if(c!=null && reader.getClassAnnotation(XmlRegistry.class,c,null/*TODO: is this right?*/)!=null) {
 371             if(!registries.containsKey(nav.getPackageName(c)))
 372                 addRegistry(c,null);
 373             return null;    // TODO: is this correct?
 374         } else
 375             return getTypeInfo(ref.type,null);
 376     }
 377 
 378 
 379     protected EnumLeafInfoImpl<T,C,F,M> createEnumLeafInfo(C clazz,Locatable upstream) {
 380         return new EnumLeafInfoImpl<T,C,F,M>(this,upstream,clazz,nav.use(clazz));
 381     }
 382 
 383     protected ClassInfoImpl<T,C,F,M> createClassInfo(C clazz, Locatable upstream ) {
 384         return new ClassInfoImpl<T,C,F,M>(this,upstream,clazz);
 385     }
 386 
 387     protected ElementInfoImpl<T,C,F,M> createElementInfo(
 388         RegistryInfoImpl<T,C,F,M> registryInfo, M m) throws IllegalAnnotationException {
 389         return new ElementInfoImpl<T,C,F,M>(this,registryInfo,m);
 390     }
 391 
 392     protected ArrayInfoImpl<T,C,F,M> createArrayInfo(Locatable upstream, T arrayType) {
 393         return new ArrayInfoImpl<T, C, F, M>(this,upstream,arrayType);
 394     }
 395 
 396 
 397     /**
 398      * Visits a class with {@link XmlRegistry} and records all the element mappings
 399      * in it.
 400      */
 401     public RegistryInfo<T,C> addRegistry(C registryClass, Locatable upstream ) {
 402         return new RegistryInfoImpl<T,C,F,M>(this,upstream,registryClass);
 403     }
 404 
 405     /**
 406      * Gets a {@link RegistryInfo} for the given package.
 407      *
 408      * @return
 409      *      null if no registry exists for the package.
 410      *      unlike other getXXX methods on this class,
 411      *      this method is side-effect free.
 412      */
 413     public RegistryInfo<T,C> getRegistry(String packageName) {
 414         return registries.get(packageName);
 415     }
 416 
 417     private boolean linked;
 418 
 419     /**
 420      * Called after all the classes are added to the type set
 421      * to "link" them together.
 422      *
 423      * <p>
 424      * Don't expose implementation classes in the signature.
 425      *
 426      * @return
 427      *      fully built {@link TypeInfoSet} that represents the model,
 428      *      or null if there was an error.
 429      */
 430     public TypeInfoSet<T,C,F,M> link() {
 431 
 432         assert !linked;
 433         linked = true;
 434 
 435         for( ElementInfoImpl ei : typeInfoSet.getAllElements() )
 436             ei.link();
 437 
 438         for( ClassInfoImpl ci : typeInfoSet.beans().values() )
 439             ci.link();
 440 
 441         for( EnumLeafInfoImpl li : typeInfoSet.enums().values() )
 442             li.link();
 443 
 444         if(hadError)
 445             return null;
 446         else
 447             return typeInfoSet;
 448     }
 449 
 450 //
 451 //
 452 // error handling
 453 //
 454 //
 455 
 456     /**
 457      * Sets the error handler that receives errors discovered during the model building.
 458      *
 459      * @param errorHandler
 460      *      can be null.
 461      */
 462     public void setErrorHandler(ErrorHandler errorHandler) {
 463         this.errorHandler = errorHandler;
 464     }
 465 
 466     public final void reportError(IllegalAnnotationException e) {
 467         hadError = true;
 468         if(errorHandler!=null)
 469             errorHandler.error(e);
 470     }
 471 
 472     public boolean isReplaced(C sc) {
 473         return subclassReplacements.containsKey(sc);
 474     }
 475 
 476     @Override
 477     public Navigator<T, C, F, M> getNavigator() {
 478         return nav;
 479     }
 480 
 481     @Override
 482     public AnnotationReader<T, C, F, M> getReader() {
 483         return reader;
 484     }
 485 }