1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/share/jaxws_classes/com/sun/tools/internal/xjc/reader/internalizer/DOMForest.java Tue Mar 06 16:09:35 2012 -0800 1.3 @@ -0,0 +1,571 @@ 1.4 +/* 1.5 + * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. 1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.7 + * 1.8 + * This code is free software; you can redistribute it and/or modify it 1.9 + * under the terms of the GNU General Public License version 2 only, as 1.10 + * published by the Free Software Foundation. Oracle designates this 1.11 + * particular file as subject to the "Classpath" exception as provided 1.12 + * by Oracle in the LICENSE file that accompanied this code. 1.13 + * 1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1.17 + * version 2 for more details (a copy is included in the LICENSE file that 1.18 + * accompanied this code). 1.19 + * 1.20 + * You should have received a copy of the GNU General Public License version 1.21 + * 2 along with this work; if not, write to the Free Software Foundation, 1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1.23 + * 1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 1.25 + * or visit www.oracle.com if you need additional information or have any 1.26 + * questions. 1.27 + */ 1.28 + 1.29 +package com.sun.tools.internal.xjc.reader.internalizer; 1.30 + 1.31 +import java.io.IOException; 1.32 +import java.io.OutputStream; 1.33 +import java.io.OutputStreamWriter; 1.34 +import java.util.ArrayList; 1.35 +import java.util.Collections; 1.36 +import java.util.HashMap; 1.37 +import java.util.HashSet; 1.38 +import java.util.List; 1.39 +import java.util.Map; 1.40 +import java.util.Set; 1.41 + 1.42 +import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; 1.43 +import javax.xml.parsers.DocumentBuilder; 1.44 +import javax.xml.parsers.DocumentBuilderFactory; 1.45 +import javax.xml.parsers.ParserConfigurationException; 1.46 +import javax.xml.parsers.SAXParserFactory; 1.47 +import javax.xml.stream.XMLStreamException; 1.48 +import javax.xml.stream.XMLStreamReader; 1.49 +import javax.xml.transform.Source; 1.50 +import javax.xml.transform.Transformer; 1.51 +import javax.xml.transform.TransformerException; 1.52 +import javax.xml.transform.TransformerFactory; 1.53 +import javax.xml.transform.dom.DOMSource; 1.54 +import javax.xml.transform.sax.SAXResult; 1.55 +import javax.xml.transform.sax.SAXSource; 1.56 +import javax.xml.validation.SchemaFactory; 1.57 + 1.58 +import com.sun.istack.internal.NotNull; 1.59 +import com.sun.istack.internal.XMLStreamReaderToContentHandler; 1.60 +import com.sun.tools.internal.xjc.ErrorReceiver; 1.61 +import com.sun.tools.internal.xjc.Options; 1.62 +import com.sun.tools.internal.xjc.reader.Const; 1.63 +import com.sun.tools.internal.xjc.reader.xmlschema.parser.SchemaConstraintChecker; 1.64 +import com.sun.tools.internal.xjc.util.ErrorReceiverFilter; 1.65 +import com.sun.xml.internal.bind.marshaller.DataWriter; 1.66 +import com.sun.xml.internal.xsom.parser.JAXPParser; 1.67 +import com.sun.xml.internal.xsom.parser.XMLParser; 1.68 + 1.69 +import org.w3c.dom.Document; 1.70 +import org.w3c.dom.Element; 1.71 +import org.xml.sax.ContentHandler; 1.72 +import org.xml.sax.EntityResolver; 1.73 +import org.xml.sax.InputSource; 1.74 +import org.xml.sax.SAXException; 1.75 +import org.xml.sax.SAXParseException; 1.76 +import org.xml.sax.XMLReader; 1.77 +import org.xml.sax.helpers.XMLFilterImpl; 1.78 + 1.79 + 1.80 +/** 1.81 + * Builds a DOM forest and maintains association from 1.82 + * system IDs to DOM trees. 1.83 + * 1.84 + * <p> 1.85 + * A forest is a transitive reflexive closure of referenced documents. 1.86 + * IOW, if a document is in a forest, all the documents referenced from 1.87 + * it is in a forest, too. To support this semantics, {@link DOMForest} 1.88 + * uses {@link InternalizationLogic} to find referenced documents. 1.89 + * 1.90 + * <p> 1.91 + * Some documents are marked as "root"s, meaning those documents were 1.92 + * put into a forest explicitly, not because it is referenced from another 1.93 + * document. (However, a root document can be referenced from other 1.94 + * documents, too.) 1.95 + * 1.96 + * @author 1.97 + * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) 1.98 + */ 1.99 +public final class DOMForest { 1.100 + /** actual data storage map<SystemId,Document>. */ 1.101 + private final Map<String,Document> core = new HashMap<String,Document>(); 1.102 + 1.103 + /** 1.104 + * To correctly feed documents to a schema parser, we need to remember 1.105 + * which documents (of the forest) were given as the root 1.106 + * documents, and which of them are read as included/imported 1.107 + * documents. 1.108 + * 1.109 + * <p> 1.110 + * Set of system ids as strings. 1.111 + */ 1.112 + private final Set<String> rootDocuments = new HashSet<String>(); 1.113 + 1.114 + /** Stores location information for all the trees in this forest. */ 1.115 + public final LocatorTable locatorTable = new LocatorTable(); 1.116 + 1.117 + /** Stores all the outer-most <jaxb:bindings> customizations. */ 1.118 + public final Set<Element> outerMostBindings = new HashSet<Element>(); 1.119 + 1.120 + /** Used to resolve references to other schema documents. */ 1.121 + private EntityResolver entityResolver = null; 1.122 + 1.123 + /** Errors encountered during the parsing will be sent to this object. */ 1.124 + private ErrorReceiver errorReceiver = null; 1.125 + 1.126 + /** Schema language dependent part of the processing. */ 1.127 + protected final InternalizationLogic logic; 1.128 + 1.129 + private final SAXParserFactory parserFactory; 1.130 + private final DocumentBuilder documentBuilder; 1.131 + 1.132 + 1.133 + public DOMForest( 1.134 + SAXParserFactory parserFactory, DocumentBuilder documentBuilder, 1.135 + InternalizationLogic logic ) { 1.136 + 1.137 + this.parserFactory = parserFactory; 1.138 + this.documentBuilder = documentBuilder; 1.139 + this.logic = logic; 1.140 + } 1.141 + 1.142 + public DOMForest( InternalizationLogic logic ) { 1.143 + try { 1.144 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 1.145 + dbf.setNamespaceAware(true); 1.146 + this.documentBuilder = dbf.newDocumentBuilder(); 1.147 + 1.148 + this.parserFactory = SAXParserFactory.newInstance(); 1.149 + this.parserFactory.setNamespaceAware(true); 1.150 + } catch( ParserConfigurationException e ) { 1.151 + throw new AssertionError(e); 1.152 + } 1.153 + 1.154 + this.logic = logic; 1.155 + } 1.156 + 1.157 + /** 1.158 + * Gets the DOM tree associated with the specified system ID, 1.159 + * or null if none is found. 1.160 + */ 1.161 + public Document get( String systemId ) { 1.162 + Document doc = core.get(systemId); 1.163 + 1.164 + if( doc==null && systemId.startsWith("file:/") && !systemId.startsWith("file://") ) { 1.165 + // As of JDK1.4, java.net.URL.toExternal method returns URLs like 1.166 + // "file:/abc/def/ghi" which is an incorrect file protocol URL according to RFC1738. 1.167 + // Some other correctly functioning parts return the correct URLs ("file:///abc/def/ghi"), 1.168 + // and this descripancy breaks DOM look up by system ID. 1.169 + 1.170 + // this extra check solves this problem. 1.171 + doc = core.get( "file://"+systemId.substring(5) ); 1.172 + } 1.173 + 1.174 + if( doc==null && systemId.startsWith("file:") ) { 1.175 + // on Windows, filenames are case insensitive. 1.176 + // perform case-insensitive search for improved user experience 1.177 + String systemPath = getPath(systemId); 1.178 + for (String key : core.keySet()) { 1.179 + if(key.startsWith("file:") && getPath(key).equalsIgnoreCase(systemPath)) { 1.180 + doc = core.get(key); 1.181 + break; 1.182 + } 1.183 + } 1.184 + } 1.185 + 1.186 + return doc; 1.187 + } 1.188 + 1.189 + /** 1.190 + * Strips off the leading 'file:///' portion from an URL. 1.191 + */ 1.192 + private String getPath(String key) { 1.193 + key = key.substring(5); // skip 'file:' 1.194 + while(key.length()>0 && key.charAt(0)=='/') 1.195 + key = key.substring(1); 1.196 + return key; 1.197 + } 1.198 + 1.199 + /** 1.200 + * Returns a read-only set of root document system IDs. 1.201 + */ 1.202 + public Set<String> getRootDocuments() { 1.203 + return Collections.unmodifiableSet(rootDocuments); 1.204 + } 1.205 + 1.206 + /** 1.207 + * Picks one document at random and returns it. 1.208 + */ 1.209 + public Document getOneDocument() { 1.210 + for (Document dom : core.values()) { 1.211 + if (!dom.getDocumentElement().getNamespaceURI().equals(Const.JAXB_NSURI)) 1.212 + return dom; 1.213 + } 1.214 + // we should have caught this error very early on 1.215 + throw new AssertionError(); 1.216 + } 1.217 + 1.218 + /** 1.219 + * Checks the correctness of the XML Schema documents and return true 1.220 + * if it's OK. 1.221 + * 1.222 + * <p> 1.223 + * This method performs a weaker version of the tests where error messages 1.224 + * are provided without line number information. So whenever possible 1.225 + * use {@link SchemaConstraintChecker}. 1.226 + * 1.227 + * @see SchemaConstraintChecker 1.228 + */ 1.229 + public boolean checkSchemaCorrectness(ErrorReceiver errorHandler) { 1.230 + try { 1.231 + SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI); 1.232 + ErrorReceiverFilter filter = new ErrorReceiverFilter(errorHandler); 1.233 + sf.setErrorHandler(filter); 1.234 + Set<String> roots = getRootDocuments(); 1.235 + Source[] sources = new Source[roots.size()]; 1.236 + int i=0; 1.237 + for (String root : roots) { 1.238 + sources[i++] = new DOMSource(get(root),root); 1.239 + } 1.240 + sf.newSchema(sources); 1.241 + return !filter.hadError(); 1.242 + } catch (SAXException e) { 1.243 + // the errors should have been reported 1.244 + return false; 1.245 + } 1.246 + } 1.247 + 1.248 + /** 1.249 + * Gets the system ID from which the given DOM is parsed. 1.250 + * <p> 1.251 + * Poor-man's base URI. 1.252 + */ 1.253 + public String getSystemId( Document dom ) { 1.254 + for (Map.Entry<String,Document> e : core.entrySet()) { 1.255 + if (e.getValue() == dom) 1.256 + return e.getKey(); 1.257 + } 1.258 + return null; 1.259 + } 1.260 + 1.261 + public Document parse( InputSource source, boolean root ) throws SAXException { 1.262 + if( source.getSystemId()==null ) 1.263 + throw new IllegalArgumentException(); 1.264 + 1.265 + return parse( source.getSystemId(), source, root ); 1.266 + } 1.267 + 1.268 + /** 1.269 + * Parses an XML at the given location ( 1.270 + * and XMLs referenced by it) into DOM trees 1.271 + * and stores them to this forest. 1.272 + * 1.273 + * @return the parsed DOM document object. 1.274 + */ 1.275 + public Document parse( String systemId, boolean root ) throws SAXException, IOException { 1.276 + 1.277 + systemId = Options.normalizeSystemId(systemId); 1.278 + 1.279 + if( core.containsKey(systemId) ) 1.280 + // this document has already been parsed. Just ignore. 1.281 + return core.get(systemId); 1.282 + 1.283 + InputSource is=null; 1.284 + 1.285 + // allow entity resolver to find the actual byte stream. 1.286 + if( entityResolver!=null ) 1.287 + is = entityResolver.resolveEntity(null,systemId); 1.288 + if( is==null ) 1.289 + is = new InputSource(systemId); 1.290 + 1.291 + // but we still use the original system Id as the key. 1.292 + return parse( systemId, is, root ); 1.293 + } 1.294 + 1.295 + /** 1.296 + * Returns a {@link ContentHandler} to feed SAX events into. 1.297 + * 1.298 + * <p> 1.299 + * The client of this class can feed SAX events into the handler 1.300 + * to parse a document into this DOM forest. 1.301 + * 1.302 + * This version requires that the DOM object to be created and registered 1.303 + * to the map beforehand. 1.304 + */ 1.305 + private ContentHandler getParserHandler( Document dom ) { 1.306 + ContentHandler handler = new DOMBuilder(dom,locatorTable,outerMostBindings); 1.307 + handler = new WhitespaceStripper(handler,errorReceiver,entityResolver); 1.308 + handler = new VersionChecker(handler,errorReceiver,entityResolver); 1.309 + 1.310 + // insert the reference finder so that 1.311 + // included/imported schemas will be also parsed 1.312 + XMLFilterImpl f = logic.createExternalReferenceFinder(this); 1.313 + f.setContentHandler(handler); 1.314 + 1.315 + if(errorReceiver!=null) 1.316 + f.setErrorHandler(errorReceiver); 1.317 + if(entityResolver!=null) 1.318 + f.setEntityResolver(entityResolver); 1.319 + 1.320 + return f; 1.321 + } 1.322 + 1.323 + public interface Handler extends ContentHandler { 1.324 + /** 1.325 + * Gets the DOM that was built. 1.326 + */ 1.327 + public Document getDocument(); 1.328 + } 1.329 + 1.330 + private static abstract class HandlerImpl extends XMLFilterImpl implements Handler { 1.331 + } 1.332 + 1.333 + /** 1.334 + * Returns a {@link ContentHandler} to feed SAX events into. 1.335 + * 1.336 + * <p> 1.337 + * The client of this class can feed SAX events into the handler 1.338 + * to parse a document into this DOM forest. 1.339 + */ 1.340 + public Handler getParserHandler( String systemId, boolean root ) { 1.341 + final Document dom = documentBuilder.newDocument(); 1.342 + core.put( systemId, dom ); 1.343 + if(root) 1.344 + rootDocuments.add(systemId); 1.345 + 1.346 + ContentHandler handler = getParserHandler(dom); 1.347 + 1.348 + // we will register the DOM to the map once the system ID becomes available. 1.349 + // but the SAX allows the event source to not to provide that information, 1.350 + // so be prepared for such case. 1.351 + HandlerImpl x = new HandlerImpl() { 1.352 + public Document getDocument() { 1.353 + return dom; 1.354 + } 1.355 + }; 1.356 + x.setContentHandler(handler); 1.357 + 1.358 + return x; 1.359 + } 1.360 + 1.361 + /** 1.362 + * Parses the given document and add it to the DOM forest. 1.363 + * 1.364 + * @return 1.365 + * null if there was a parse error. otherwise non-null. 1.366 + */ 1.367 + public Document parse( String systemId, InputSource inputSource, boolean root ) throws SAXException { 1.368 + Document dom = documentBuilder.newDocument(); 1.369 + 1.370 + systemId = Options.normalizeSystemId(systemId); 1.371 + 1.372 + // put into the map before growing a tree, to 1.373 + // prevent recursive reference from causing infinite loop. 1.374 + core.put( systemId, dom ); 1.375 + if(root) 1.376 + rootDocuments.add(systemId); 1.377 + 1.378 + try { 1.379 + XMLReader reader = parserFactory.newSAXParser().getXMLReader(); 1.380 + reader.setContentHandler(getParserHandler(dom)); 1.381 + if(errorReceiver!=null) 1.382 + reader.setErrorHandler(errorReceiver); 1.383 + if(entityResolver!=null) 1.384 + reader.setEntityResolver(entityResolver); 1.385 + reader.parse(inputSource); 1.386 + } catch( ParserConfigurationException e ) { 1.387 + // in practice, this exception won't happen. 1.388 + errorReceiver.error(e.getMessage(),e); 1.389 + core.remove(systemId); 1.390 + rootDocuments.remove(systemId); 1.391 + return null; 1.392 + } catch( IOException e ) { 1.393 + errorReceiver.error(Messages.format(Messages.DOMFOREST_INPUTSOURCE_IOEXCEPTION, systemId, e.toString()),e); 1.394 + core.remove(systemId); 1.395 + rootDocuments.remove(systemId); 1.396 + return null; 1.397 + } 1.398 + 1.399 + return dom; 1.400 + } 1.401 + 1.402 + public Document parse( String systemId, XMLStreamReader parser, boolean root ) throws XMLStreamException { 1.403 + Document dom = documentBuilder.newDocument(); 1.404 + 1.405 + systemId = Options.normalizeSystemId(systemId); 1.406 + 1.407 + if(root) 1.408 + rootDocuments.add(systemId); 1.409 + 1.410 + if(systemId==null) 1.411 + throw new IllegalArgumentException("system id cannot be null"); 1.412 + core.put( systemId, dom ); 1.413 + 1.414 + new XMLStreamReaderToContentHandler(parser,getParserHandler(dom),false,false).bridge(); 1.415 + 1.416 + return dom; 1.417 + } 1.418 + 1.419 + /** 1.420 + * Performs internalization. 1.421 + * 1.422 + * This method should be called only once, only after all the 1.423 + * schemas are parsed. 1.424 + * 1.425 + * @return 1.426 + * the returned bindings need to be applied after schema 1.427 + * components are built. 1.428 + */ 1.429 + public SCDBasedBindingSet transform(boolean enableSCD) { 1.430 + return Internalizer.transform(this,enableSCD); 1.431 + } 1.432 + 1.433 + /** 1.434 + * Performs the schema correctness check by using JAXP 1.3. 1.435 + * 1.436 + * <p> 1.437 + * This is "weak", because {@link SchemaFactory#newSchema(Source[])} 1.438 + * doesn't handle inclusions very correctly (it ends up parsing it 1.439 + * from its original source, not in this tree), and because 1.440 + * it doesn't handle two documents for the same namespace very 1.441 + * well. 1.442 + * 1.443 + * <p> 1.444 + * We should eventually fix JAXP (and Xerces), but meanwhile 1.445 + * this weaker and potentially wrong correctness check is still 1.446 + * better than nothing when used inside JAX-WS (JAXB CLI and Ant 1.447 + * does a better job of checking this.) 1.448 + * 1.449 + * <p> 1.450 + * To receive errors, use {@link SchemaFactory#setErrorHandler(ErrorHandler)}. 1.451 + */ 1.452 + public void weakSchemaCorrectnessCheck(SchemaFactory sf) { 1.453 + List<SAXSource> sources = new ArrayList<SAXSource>(); 1.454 + for( String systemId : getRootDocuments() ) { 1.455 + Document dom = get(systemId); 1.456 + if (dom.getDocumentElement().getNamespaceURI().equals(Const.JAXB_NSURI)) 1.457 + continue; // this isn't a schema. we have to do a negative check because if we see completely unrelated ns, we want to report that as an error 1.458 + 1.459 + SAXSource ss = createSAXSource(systemId); 1.460 + try { 1.461 + ss.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes",true); 1.462 + } catch (SAXException e) { 1.463 + throw new AssertionError(e); // Xerces wants this. See 6395322. 1.464 + } 1.465 + sources.add(ss); 1.466 + } 1.467 + 1.468 + try { 1.469 + sf.newSchema(sources.toArray(new SAXSource[0])); 1.470 + } catch (SAXException e) { 1.471 + // error should have been reported. 1.472 + } catch (RuntimeException e) { 1.473 + // JAXP RI isn't very trustworthy when it comes to schema error check, 1.474 + // and we know some cases where it just dies with NPE. So handle it gracefully. 1.475 + // this masks a bug in the JAXP RI, but we need a release that we have to make. 1.476 + try { 1.477 + sf.getErrorHandler().warning( 1.478 + new SAXParseException(Messages.format( 1.479 + Messages.ERR_GENERAL_SCHEMA_CORRECTNESS_ERROR,e.getMessage()), 1.480 + null,null,-1,-1,e)); 1.481 + } catch (SAXException _) { 1.482 + // ignore 1.483 + } 1.484 + } 1.485 + } 1.486 + 1.487 + /** 1.488 + * Creates a {@link SAXSource} that, when parsed, reads from this {@link DOMForest} 1.489 + * (instead of parsing the original source identified by the system ID.) 1.490 + */ 1.491 + public @NotNull SAXSource createSAXSource(String systemId) { 1.492 + ContentHandlerNamespacePrefixAdapter reader = new ContentHandlerNamespacePrefixAdapter(new XMLFilterImpl() { 1.493 + // XMLReader that uses XMLParser to parse. We need to use XMLFilter to indrect 1.494 + // handlers, since SAX allows handlers to be changed while parsing. 1.495 + public void parse(InputSource input) throws SAXException, IOException { 1.496 + createParser().parse(input, this, this, this); 1.497 + } 1.498 + 1.499 + public void parse(String systemId) throws SAXException, IOException { 1.500 + parse(new InputSource(systemId)); 1.501 + } 1.502 + }); 1.503 + 1.504 + return new SAXSource(reader,new InputSource(systemId)); 1.505 + } 1.506 + 1.507 + /** 1.508 + * Creates {@link XMLParser} for XSOM which reads documents from 1.509 + * this DOMForest rather than doing a fresh parse. 1.510 + * 1.511 + * The net effect is that XSOM will read transformed XML Schemas 1.512 + * instead of the original documents. 1.513 + */ 1.514 + public XMLParser createParser() { 1.515 + return new DOMForestParser(this,new JAXPParser()); 1.516 + } 1.517 + 1.518 + 1.519 + 1.520 + public EntityResolver getEntityResolver() { 1.521 + return entityResolver; 1.522 + } 1.523 + 1.524 + public void setEntityResolver(EntityResolver entityResolver) { 1.525 + this.entityResolver = entityResolver; 1.526 + } 1.527 + 1.528 + public ErrorReceiver getErrorHandler() { 1.529 + return errorReceiver; 1.530 + } 1.531 + 1.532 + public void setErrorHandler(ErrorReceiver errorHandler) { 1.533 + this.errorReceiver = errorHandler; 1.534 + } 1.535 + 1.536 + /** 1.537 + * Gets all the parsed documents. 1.538 + */ 1.539 + public Document[] listDocuments() { 1.540 + return core.values().toArray(new Document[core.size()]); 1.541 + } 1.542 + 1.543 + /** 1.544 + * Gets all the system IDs of the documents. 1.545 + */ 1.546 + public String[] listSystemIDs() { 1.547 + return core.keySet().toArray(new String[core.keySet().size()]); 1.548 + } 1.549 + 1.550 + /** 1.551 + * Dumps the contents of the forest to the specified stream. 1.552 + * 1.553 + * This is a debug method. As such, error handling is sloppy. 1.554 + */ 1.555 + public void dump( OutputStream out ) throws IOException { 1.556 + try { 1.557 + // create identity transformer 1.558 + Transformer it = TransformerFactory.newInstance().newTransformer(); 1.559 + 1.560 + for (Map.Entry<String, Document> e : core.entrySet()) { 1.561 + out.write( ("---<< "+e.getKey()+'\n').getBytes() ); 1.562 + 1.563 + DataWriter dw = new DataWriter(new OutputStreamWriter(out),null); 1.564 + dw.setIndentStep(" "); 1.565 + it.transform( new DOMSource(e.getValue()), 1.566 + new SAXResult(dw)); 1.567 + 1.568 + out.write( "\n\n\n".getBytes() ); 1.569 + } 1.570 + } catch( TransformerException e ) { 1.571 + e.printStackTrace(); 1.572 + } 1.573 + } 1.574 +}