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