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