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