1 /* 2 * Copyright (c) 1997, 2015, 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.tools.internal.xjc; 27 28 import java.io.IOException; 29 import java.io.StringReader; 30 31 import com.sun.codemodel.internal.JCodeModel; 32 import com.sun.tools.internal.xjc.model.Model; 33 import com.sun.tools.internal.xjc.reader.Const; 34 import com.sun.tools.internal.xjc.reader.ExtensionBindingChecker; 35 import com.sun.tools.internal.xjc.reader.dtd.TDTDReader; 36 import com.sun.tools.internal.xjc.reader.internalizer.DOMForest; 37 import com.sun.tools.internal.xjc.reader.internalizer.DOMForestScanner; 38 import com.sun.tools.internal.xjc.reader.internalizer.InternalizationLogic; 39 import com.sun.tools.internal.xjc.reader.internalizer.SCDBasedBindingSet; 40 import com.sun.tools.internal.xjc.reader.internalizer.VersionChecker; 41 import com.sun.tools.internal.xjc.reader.xmlschema.BGMBuilder; 42 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.AnnotationParserFactoryImpl; 43 import com.sun.tools.internal.xjc.reader.xmlschema.parser.CustomizationContextChecker; 44 import com.sun.tools.internal.xjc.reader.xmlschema.parser.IncorrectNamespaceURIChecker; 45 import com.sun.tools.internal.xjc.reader.xmlschema.parser.SchemaConstraintChecker; 46 import com.sun.tools.internal.xjc.reader.xmlschema.parser.XMLSchemaInternalizationLogic; 47 import com.sun.tools.internal.xjc.util.ErrorReceiverFilter; 48 import com.sun.xml.internal.bind.v2.util.XmlFactory; 49 import com.sun.xml.internal.xsom.XSSchemaSet; 50 import com.sun.xml.internal.xsom.parser.JAXPParser; 51 import com.sun.xml.internal.xsom.parser.XMLParser; 52 import com.sun.xml.internal.xsom.parser.XSOMParser; 53 import javax.xml.XMLConstants; 54 55 import org.w3c.dom.Document; 56 import org.w3c.dom.Element; 57 import org.w3c.dom.NodeList; 58 import org.xml.sax.Attributes; 59 import org.xml.sax.ContentHandler; 60 import org.xml.sax.EntityResolver; 61 import org.xml.sax.ErrorHandler; 62 import org.xml.sax.InputSource; 63 import org.xml.sax.SAXException; 64 import org.xml.sax.SAXParseException; 65 import org.xml.sax.helpers.XMLFilterImpl; 66 67 /** 68 * Builds a {@link Model} object. 69 * 70 * This is an utility class that makes it easy to load a grammar object 71 * from various sources. 72 * 73 * @author 74 * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 75 */ 76 public final class ModelLoader { 77 78 private final Options opt; 79 private final ErrorReceiverFilter errorReceiver; 80 private final JCodeModel codeModel; 81 /** 82 * {@link DOMForest#transform(boolean)} creates this on the side. 83 */ 84 private SCDBasedBindingSet scdBasedBindingSet; 85 86 87 /** 88 * A convenience method to load schemas into a {@link Model}. 89 */ 90 public static Model load( Options opt, JCodeModel codeModel, ErrorReceiver er ) { 91 return new ModelLoader(opt,codeModel,er).load(); 92 } 93 94 95 public ModelLoader(Options _opt, JCodeModel _codeModel, ErrorReceiver er) { 96 this.opt = _opt; 97 this.codeModel = _codeModel; 98 this.errorReceiver = new ErrorReceiverFilter(er); 99 } 100 101 @SuppressWarnings("CallToThreadDumpStack") 102 private Model load() { 103 Model grammar; 104 105 if(!sanityCheck()) 106 return null; 107 108 109 try { 110 switch (opt.getSchemaLanguage()) { 111 case DTD : 112 // TODO: make sure that bindFiles,size()<=1 113 InputSource bindFile = null; 114 if (opt.getBindFiles().length > 0) 115 bindFile = opt.getBindFiles()[0]; 116 // if there is no binding file, make a dummy one. 117 if (bindFile == null) { 118 // if no binding information is specified, provide a default 119 bindFile = 120 new InputSource( 121 new StringReader( 122 "<?xml version='1.0'?><xml-java-binding-schema><options package='" 123 + (opt.defaultPackage==null?"generated":opt.defaultPackage) 124 + "'/></xml-java-binding-schema>")); 125 } 126 127 checkTooManySchemaErrors(); 128 grammar = loadDTD(opt.getGrammars()[0], bindFile ); 129 break; 130 131 case WSDL: 132 grammar = annotateXMLSchema( loadWSDL() ); 133 break; 134 135 case XMLSCHEMA: 136 grammar = annotateXMLSchema( loadXMLSchema() ); 137 break; 138 139 default : 140 throw new AssertionError(); // assertion failed 141 } 142 143 if (errorReceiver.hadError()) { 144 grammar = null; 145 } else { 146 grammar.setPackageLevelAnnotations(opt.packageLevelAnnotations); 147 } 148 149 return grammar; 150 151 } catch (SAXException e) { 152 // parsing error in the input document. 153 // this error must have been reported to the user vis error handler 154 // so don't print it again. 155 if (opt.verbose) { 156 // however, a bug in XJC might throw unexpected SAXException. 157 // thus when one is debugging, it is useful to print what went 158 // wrong. 159 if (e.getException() != null) 160 e.getException().printStackTrace(); 161 else 162 e.printStackTrace(); 163 } 164 return null; 165 } catch (AbortException e) { 166 // error should have been reported already, since this is requested by the error receiver 167 return null; 168 } 169 } 170 171 172 173 /** 174 * Do some extra checking and return false if the compilation 175 * should abort. 176 */ 177 private boolean sanityCheck() { 178 if( opt.getSchemaLanguage()==Language.XMLSCHEMA ) { 179 Language guess = opt.guessSchemaLanguage(); 180 181 String[] msg = null; 182 switch(guess) { 183 case DTD: 184 msg = new String[]{"DTD","-dtd"}; 185 break; 186 case WSDL: 187 msg = new String[]{"WSDL","-wsdl"}; 188 break; 189 } 190 if( msg!=null ) 191 errorReceiver.warning( null, 192 Messages.format( 193 Messages.EXPERIMENTAL_LANGUAGE_WARNING, 194 msg[0], msg[1] )); 195 } 196 return true; 197 } 198 199 200 /** 201 * {@link XMLParser} implementation that adds additional processors into the chain. 202 * 203 * <p> 204 * This parser will parse a DOM forest as: 205 * DOMForestParser --> 206 * ExtensionBindingChecker --> 207 * ProhibitedFeatureFilter --> 208 * XSOMParser 209 */ 210 private class XMLSchemaParser implements XMLParser { 211 private final XMLParser baseParser; 212 213 private XMLSchemaParser(XMLParser baseParser) { 214 this.baseParser = baseParser; 215 } 216 217 public void parse(InputSource source, ContentHandler handler, 218 ErrorHandler errorHandler, EntityResolver entityResolver ) throws SAXException, IOException { 219 // set up the chain of handlers. 220 handler = wrapBy( new ExtensionBindingChecker(XMLConstants.W3C_XML_SCHEMA_NS_URI,opt,errorReceiver), handler ); 221 handler = wrapBy( new IncorrectNamespaceURIChecker(errorReceiver), handler ); 222 handler = wrapBy( new CustomizationContextChecker(errorReceiver), handler ); 223 // handler = wrapBy( new VersionChecker(controller), handler ); 224 225 baseParser.parse( source, handler, errorHandler, entityResolver ); 226 } 227 /** 228 * Wraps the specified content handler by a filter. 229 * It is little awkward to use a helper implementation class like XMLFilterImpl 230 * as the method parameter, but this simplifies the code. 231 */ 232 private ContentHandler wrapBy( XMLFilterImpl filter, ContentHandler handler ) { 233 filter.setContentHandler(handler); 234 return filter; 235 } 236 } 237 238 private void checkTooManySchemaErrors() { 239 if( opt.getGrammars().length!=1 ) 240 errorReceiver.error(null,Messages.format(Messages.ERR_TOO_MANY_SCHEMA)); 241 } 242 243 /** 244 * Parses a DTD file into an annotated grammar. 245 * 246 * @param source 247 * DTD file 248 * @param bindFile 249 * External binding file. 250 */ 251 private Model loadDTD( InputSource source, InputSource bindFile) { 252 253 // parse the schema as a DTD. 254 return TDTDReader.parse( 255 source, 256 bindFile, 257 errorReceiver, 258 opt); 259 } 260 261 /** 262 * Builds DOMForest and performs the internalization. 263 * 264 * @throws SAXException 265 * when a fatal error happens 266 */ 267 public DOMForest buildDOMForest( InternalizationLogic logic ) 268 throws SAXException { 269 270 // parse into DOM forest 271 DOMForest forest = new DOMForest(logic, opt); 272 273 forest.setErrorHandler(errorReceiver); 274 if(opt.entityResolver!=null) 275 forest.setEntityResolver(opt.entityResolver); 276 277 // parse source grammars 278 for (InputSource value : opt.getGrammars()) { 279 errorReceiver.pollAbort(); 280 forest.parse(value, true); 281 } 282 283 // parse external binding files 284 for (InputSource value : opt.getBindFiles()) { 285 errorReceiver.pollAbort(); 286 Document dom = forest.parse(value, true); 287 if(dom==null) continue; // error must have been reported 288 Element root = dom.getDocumentElement(); 289 // TODO: it somehow doesn't feel right to do a validation in the Driver class. 290 // think about moving it to somewhere else. 291 if (!fixNull(root.getNamespaceURI()).equals(Const.JAXB_NSURI) 292 || !root.getLocalName().equals("bindings")) 293 errorReceiver.error(new SAXParseException(Messages.format(Messages.ERR_NOT_A_BINDING_FILE, 294 root.getNamespaceURI(), 295 root.getLocalName()), 296 null, 297 value.getSystemId(), 298 -1, -1)); 299 } 300 301 scdBasedBindingSet = forest.transform(opt.isExtensionMode()); 302 303 return forest; 304 } 305 306 private String fixNull(String s) { 307 if(s==null) return ""; 308 else return s; 309 } 310 311 /** 312 * Parses a set of XML Schema files into an annotated grammar. 313 */ 314 public XSSchemaSet loadXMLSchema() throws SAXException { 315 316 if( opt.strictCheck && !SchemaConstraintChecker.check(opt.getGrammars(),errorReceiver,opt.entityResolver, opt.disableXmlSecurity)) { 317 // schema error. error should have been reported 318 return null; 319 } 320 321 if(opt.getBindFiles().length==0) { 322 // no external binding. try the speculative no DOMForest execution, 323 // which is faster if the speculation succeeds. 324 try { 325 return createXSOMSpeculative(); 326 } catch( SpeculationFailure e) { 327 // failed. go the slow way 328 } 329 } 330 331 // the default slower way is to parse everything into DOM first. 332 // so that we can take external annotations into account. 333 DOMForest forest = buildDOMForest( new XMLSchemaInternalizationLogic() ); 334 return createXSOM(forest, scdBasedBindingSet); 335 } 336 337 /** 338 * Parses a set of schemas inside a WSDL file. 339 * 340 * A WSDL file may contain multiple <xsd:schema> elements. 341 */ 342 private XSSchemaSet loadWSDL() 343 throws SAXException { 344 345 346 // build DOMForest just like we handle XML Schema 347 DOMForest forest = buildDOMForest( new XMLSchemaInternalizationLogic() ); 348 349 DOMForestScanner scanner = new DOMForestScanner(forest); 350 351 XSOMParser xsomParser = createXSOMParser( forest ); 352 353 // find <xsd:schema>s and parse them individually 354 for( InputSource grammar : opt.getGrammars() ) { 355 Document wsdlDom = forest.get( grammar.getSystemId() ); 356 if (wsdlDom == null) { 357 String systemId = Options.normalizeSystemId(grammar.getSystemId()); 358 if (forest.get(systemId) != null) { 359 grammar.setSystemId(systemId); 360 wsdlDom = forest.get( grammar.getSystemId() ); 361 } 362 } 363 364 NodeList schemas = wsdlDom.getElementsByTagNameNS(XMLConstants.W3C_XML_SCHEMA_NS_URI,"schema"); 365 for( int i=0; i<schemas.getLength(); i++ ) 366 scanner.scan( (Element)schemas.item(i), xsomParser.getParserHandler() ); 367 } 368 return xsomParser.getResult(); 369 } 370 371 /** 372 * Annotates the obtained schema set. 373 * 374 * @return 375 * null if an error happens. In that case, the error messages 376 * will be properly reported to the controller by this method. 377 */ 378 public Model annotateXMLSchema(XSSchemaSet xs) { 379 if (xs == null) 380 return null; 381 return BGMBuilder.build(xs, codeModel, errorReceiver, opt); 382 } 383 384 /** 385 * Potentially problematic - make sure the parser instance passed is initialized 386 * with proper security feature. 387 * 388 * @param parser 389 * @return 390 */ 391 public XSOMParser createXSOMParser(XMLParser parser) { 392 // set up other parameters to XSOMParser 393 XSOMParser reader = new XSOMParser(new XMLSchemaParser(parser)); 394 reader.setAnnotationParser(new AnnotationParserFactoryImpl(opt)); 395 reader.setErrorHandler(errorReceiver); 396 reader.setEntityResolver(opt.entityResolver); 397 return reader; 398 } 399 400 public XSOMParser createXSOMParser(final DOMForest forest) { 401 XSOMParser p = createXSOMParser(forest.createParser()); 402 p.setEntityResolver(new EntityResolver() { 403 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 404 // DOMForest only parses documents that are reachable through systemIds, 405 // and it won't pick up references like <xs:import namespace="..." /> without 406 // @schemaLocation. So we still need to use an entity resolver here to resolve 407 // these references, yet we don't want to just run them blindly, since if we do that 408 // DOMForestParser always get the translated system ID when catalog is used 409 // (where DOMForest records trees with their original system IDs.) 410 if(systemId!=null && forest.get(systemId)!=null) 411 return new InputSource(systemId); 412 if(opt.entityResolver!=null) 413 return opt.entityResolver.resolveEntity(publicId,systemId); 414 415 return null; 416 } 417 }); 418 return p; 419 } 420 421 422 private static final class SpeculationFailure extends Error {} 423 424 private static final class SpeculationChecker extends XMLFilterImpl { 425 @Override 426 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 427 if(localName.equals("bindings") && uri.equals(Const.JAXB_NSURI)) 428 throw new SpeculationFailure(); 429 super.startElement(uri,localName,qName,attributes); 430 } 431 } 432 433 /** 434 * Parses schemas directly into XSOM by assuming that there's 435 * no external annotations. 436 * <p> 437 * When an external annotation is found, a {@link SpeculationFailure} is thrown, 438 * and we will do it all over again by using the slow way. 439 */ 440 private XSSchemaSet createXSOMSpeculative() throws SAXException, SpeculationFailure { 441 442 // check if the schema contains external binding files. If so, speculation is a failure. 443 444 XMLParser parser = new XMLParser() { 445 private final JAXPParser base = new JAXPParser(XmlFactory.createParserFactory(opt.disableXmlSecurity)); 446 447 public void parse(InputSource source, ContentHandler handler, 448 ErrorHandler errorHandler, EntityResolver entityResolver ) throws SAXException, IOException { 449 // set up the chain of handlers. 450 handler = wrapBy( new SpeculationChecker(), handler ); 451 handler = wrapBy( new VersionChecker(null,errorReceiver,entityResolver), handler ); 452 453 base.parse( source, handler, errorHandler, entityResolver ); 454 } 455 /** 456 * Wraps the specified content handler by a filter. 457 * It is little awkward to use a helper implementation class like XMLFilterImpl 458 * as the method parameter, but this simplifies the code. 459 */ 460 private ContentHandler wrapBy( XMLFilterImpl filter, ContentHandler handler ) { 461 filter.setContentHandler(handler); 462 return filter; 463 } 464 }; 465 466 XSOMParser reader = createXSOMParser(parser); 467 468 // parse source grammars 469 for (InputSource value : opt.getGrammars()) 470 reader.parse(value); 471 472 return reader.getResult(); 473 } 474 475 /** 476 * Parses a {@link DOMForest} into a {@link XSSchemaSet}. 477 * 478 * @return 479 * null if the parsing failed. 480 */ 481 public XSSchemaSet createXSOM(DOMForest forest, SCDBasedBindingSet scdBasedBindingSet) throws SAXException { 482 // set up other parameters to XSOMParser 483 XSOMParser reader = createXSOMParser(forest); 484 485 // re-parse the transformed schemas 486 for (String systemId : forest.getRootDocuments()) { 487 errorReceiver.pollAbort(); 488 Document dom = forest.get(systemId); 489 if (!dom.getDocumentElement().getNamespaceURI().equals(Const.JAXB_NSURI)) { 490 reader.parse(systemId); 491 } 492 } 493 494 XSSchemaSet result = reader.getResult(); 495 496 if(result!=null) 497 scdBasedBindingSet.apply(result,errorReceiver); 498 499 return result; 500 } 501 502 }