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 &lt;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 }