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 }