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 }