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