src/share/jaxws_classes/com/sun/tools/internal/xjc/reader/internalizer/DOMForest.java

Fri, 04 Oct 2013 16:21:34 +0100

author
mkos
date
Fri, 04 Oct 2013 16:21:34 +0100
changeset 408
b0610cd08440
parent 397
b99d7e355d4b
child 637
9c07ef4934dd
permissions
-rw-r--r--

8025054: Update JAX-WS RI integration to 2.2.9-b130926.1035
Reviewed-by: chegar

     1 /*
     2  * Copyright (c) 1997, 2013, 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  */
    26 package com.sun.tools.internal.xjc.reader.internalizer;
    28 import com.sun.istack.internal.NotNull;
    29 import com.sun.istack.internal.XMLStreamReaderToContentHandler;
    30 import com.sun.tools.internal.xjc.ErrorReceiver;
    31 import com.sun.tools.internal.xjc.Options;
    32 import com.sun.tools.internal.xjc.reader.Const;
    33 import com.sun.tools.internal.xjc.util.ErrorReceiverFilter;
    34 import com.sun.xml.internal.bind.marshaller.DataWriter;
    35 import com.sun.xml.internal.bind.v2.util.XmlFactory;
    36 import com.sun.xml.internal.xsom.parser.JAXPParser;
    37 import com.sun.xml.internal.xsom.parser.XMLParser;
    38 import org.w3c.dom.Document;
    39 import org.w3c.dom.Element;
    40 import org.xml.sax.*;
    41 import org.xml.sax.helpers.XMLFilterImpl;
    43 import javax.xml.parsers.DocumentBuilder;
    44 import javax.xml.parsers.DocumentBuilderFactory;
    45 import javax.xml.parsers.ParserConfigurationException;
    46 import javax.xml.parsers.SAXParserFactory;
    47 import javax.xml.stream.XMLStreamException;
    48 import javax.xml.stream.XMLStreamReader;
    49 import javax.xml.transform.Source;
    50 import javax.xml.transform.Transformer;
    51 import javax.xml.transform.TransformerException;
    52 import javax.xml.transform.TransformerFactory;
    53 import javax.xml.transform.dom.DOMSource;
    54 import javax.xml.transform.sax.SAXResult;
    55 import javax.xml.transform.sax.SAXSource;
    56 import javax.xml.validation.SchemaFactory;
    57 import java.io.IOException;
    58 import java.io.OutputStream;
    59 import java.io.OutputStreamWriter;
    60 import java.util.*;
    62 import static com.sun.xml.internal.bind.v2.util.XmlFactory.allowExternalAccess;
    63 import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
    66 /**
    67  * Builds a DOM forest and maintains association from
    68  * system IDs to DOM trees.
    69  *
    70  * <p>
    71  * A forest is a transitive reflexive closure of referenced documents.
    72  * IOW, if a document is in a forest, all the documents referenced from
    73  * it is in a forest, too. To support this semantics, {@link DOMForest}
    74  * uses {@link InternalizationLogic} to find referenced documents.
    75  *
    76  * <p>
    77  * Some documents are marked as "root"s, meaning those documents were
    78  * put into a forest explicitly, not because it is referenced from another
    79  * document. (However, a root document can be referenced from other
    80  * documents, too.)
    81  *
    82  * @author
    83  *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
    84  */
    85 public final class DOMForest {
    86     /** actual data storage map&lt;SystemId,Document>. */
    87     private final Map<String,Document> core = new HashMap<String,Document>();
    89     /**
    90      * To correctly feed documents to a schema parser, we need to remember
    91      * which documents (of the forest) were given as the root
    92      * documents, and which of them are read as included/imported
    93      * documents.
    94      *
    95      * <p>
    96      * Set of system ids as strings.
    97      */
    98     private final Set<String> rootDocuments = new HashSet<String>();
   100     /** Stores location information for all the trees in this forest. */
   101     public final LocatorTable locatorTable = new LocatorTable();
   103     /** Stores all the outer-most &lt;jaxb:bindings> customizations. */
   104     public final Set<Element> outerMostBindings = new HashSet<Element>();
   106     /** Used to resolve references to other schema documents. */
   107     private EntityResolver entityResolver = null;
   109     /** Errors encountered during the parsing will be sent to this object. */
   110     private ErrorReceiver errorReceiver = null;
   112     /** Schema language dependent part of the processing. */
   113     protected final InternalizationLogic logic;
   115     private final SAXParserFactory parserFactory;
   116     private final DocumentBuilder documentBuilder;
   118     private final Options options;
   120     public DOMForest(
   121         SAXParserFactory parserFactory, DocumentBuilder documentBuilder,
   122         InternalizationLogic logic ) {
   124         this.parserFactory = parserFactory;
   125         this.documentBuilder = documentBuilder;
   126         this.logic = logic;
   127         this.options = null;
   128     }
   130     public DOMForest( InternalizationLogic logic, Options opt ) {
   132         if (opt == null) throw new AssertionError("Options object null");
   133         this.options = opt;
   135         try {
   136             DocumentBuilderFactory dbf = XmlFactory.createDocumentBuilderFactory(opt.disableXmlSecurity);
   137             this.documentBuilder = dbf.newDocumentBuilder();
   138             this.parserFactory = XmlFactory.createParserFactory(opt.disableXmlSecurity);
   139         } catch( ParserConfigurationException e ) {
   140             throw new AssertionError(e);
   141         }
   143         this.logic = logic;
   144     }
   146     /**
   147      * Gets the DOM tree associated with the specified system ID,
   148      * or null if none is found.
   149      */
   150     public Document get( String systemId ) {
   151         Document doc = core.get(systemId);
   153         if( doc==null && systemId.startsWith("file:/") && !systemId.startsWith("file://") ) {
   154             // As of JDK1.4, java.net.URL.toExternal method returns URLs like
   155             // "file:/abc/def/ghi" which is an incorrect file protocol URL according to RFC1738.
   156             // Some other correctly functioning parts return the correct URLs ("file:///abc/def/ghi"),
   157             // and this descripancy breaks DOM look up by system ID.
   159             // this extra check solves this problem.
   160             doc = core.get( "file://"+systemId.substring(5) );
   161         }
   163         if( doc==null && systemId.startsWith("file:") ) {
   164             // on Windows, filenames are case insensitive.
   165             // perform case-insensitive search for improved user experience
   166             String systemPath = getPath(systemId);
   167             for (String key : core.keySet()) {
   168                 if(key.startsWith("file:") && getPath(key).equalsIgnoreCase(systemPath)) {
   169                     doc = core.get(key);
   170                     break;
   171                 }
   172             }
   173         }
   175         return doc;
   176     }
   178     /**
   179      * Strips off the leading 'file:///' portion from an URL.
   180      */
   181     private String getPath(String key) {
   182         key = key.substring(5); // skip 'file:'
   183         while(key.length()>0 && key.charAt(0)=='/') {
   184             key = key.substring(1);
   185         }
   186         return key;
   187     }
   189     /**
   190      * Returns a read-only set of root document system IDs.
   191      */
   192     public Set<String> getRootDocuments() {
   193         return Collections.unmodifiableSet(rootDocuments);
   194     }
   196     /**
   197      * Picks one document at random and returns it.
   198      */
   199     public Document getOneDocument() {
   200         for (Document dom : core.values()) {
   201             if (!dom.getDocumentElement().getNamespaceURI().equals(Const.JAXB_NSURI))
   202                 return dom;
   203         }
   204         // we should have caught this error very early on
   205         throw new AssertionError();
   206     }
   208     /**
   209      * Checks the correctness of the XML Schema documents and return true
   210      * if it's OK.
   211      *
   212      * <p>
   213      * This method performs a weaker version of the tests where error messages
   214      * are provided without line number information. So whenever possible
   215      * use {@link SchemaConstraintChecker}.
   216      *
   217      * @see SchemaConstraintChecker
   218      */
   219     public boolean checkSchemaCorrectness(ErrorReceiver errorHandler) {
   220         try {
   221             boolean disableXmlSecurity = false;
   222             if (options != null) {
   223                 disableXmlSecurity = options.disableXmlSecurity;
   224             }
   225             SchemaFactory sf = XmlFactory.createSchemaFactory(W3C_XML_SCHEMA_NS_URI, disableXmlSecurity);
   226             ErrorReceiverFilter filter = new ErrorReceiverFilter(errorHandler);
   227             sf.setErrorHandler(filter);
   228             Set<String> roots = getRootDocuments();
   229             Source[] sources = new Source[roots.size()];
   230             int i=0;
   231             for (String root : roots) {
   232                 sources[i++] = new DOMSource(get(root),root);
   233             }
   234             sf.newSchema(sources);
   235             return !filter.hadError();
   236         } catch (SAXException e) {
   237             // the errors should have been reported
   238             return false;
   239         }
   240     }
   242     /**
   243      * Gets the system ID from which the given DOM is parsed.
   244      * <p>
   245      * Poor-man's base URI.
   246      */
   247     public String getSystemId( Document dom ) {
   248         for (Map.Entry<String,Document> e : core.entrySet()) {
   249             if (e.getValue() == dom)
   250                 return e.getKey();
   251         }
   252         return null;
   253     }
   255     public Document parse( InputSource source, boolean root ) throws SAXException {
   256         if( source.getSystemId()==null )
   257             throw new IllegalArgumentException();
   259         return parse( source.getSystemId(), source, root );
   260     }
   262     /**
   263      * Parses an XML at the given location (
   264      * and XMLs referenced by it) into DOM trees
   265      * and stores them to this forest.
   266      *
   267      * @return the parsed DOM document object.
   268      */
   269     public Document parse( String systemId, boolean root ) throws SAXException, IOException {
   271         systemId = Options.normalizeSystemId(systemId);
   273         if( core.containsKey(systemId) )
   274             // this document has already been parsed. Just ignore.
   275             return core.get(systemId);
   277         InputSource is=null;
   279         // allow entity resolver to find the actual byte stream.
   280         if( entityResolver!=null )
   281             is = entityResolver.resolveEntity(null,systemId);
   282         if( is==null )
   283             is = new InputSource(systemId);
   285         // but we still use the original system Id as the key.
   286         return parse( systemId, is, root );
   287     }
   289     /**
   290      * Returns a {@link ContentHandler} to feed SAX events into.
   291      *
   292      * <p>
   293      * The client of this class can feed SAX events into the handler
   294      * to parse a document into this DOM forest.
   295      *
   296      * This version requires that the DOM object to be created and registered
   297      * to the map beforehand.
   298      */
   299     private ContentHandler getParserHandler( Document dom ) {
   300         ContentHandler handler = new DOMBuilder(dom,locatorTable,outerMostBindings);
   301         handler = new WhitespaceStripper(handler,errorReceiver,entityResolver);
   302         handler = new VersionChecker(handler,errorReceiver,entityResolver);
   304         // insert the reference finder so that
   305         // included/imported schemas will be also parsed
   306         XMLFilterImpl f = logic.createExternalReferenceFinder(this);
   307         f.setContentHandler(handler);
   309         if(errorReceiver!=null)
   310             f.setErrorHandler(errorReceiver);
   311         if(entityResolver!=null)
   312             f.setEntityResolver(entityResolver);
   314         return f;
   315     }
   317     public interface Handler extends ContentHandler {
   318         /**
   319          * Gets the DOM that was built.
   320          */
   321         public Document getDocument();
   322     }
   324     private static abstract class HandlerImpl extends XMLFilterImpl implements Handler {
   325     }
   327     /**
   328      * Returns a {@link ContentHandler} to feed SAX events into.
   329      *
   330      * <p>
   331      * The client of this class can feed SAX events into the handler
   332      * to parse a document into this DOM forest.
   333      */
   334     public Handler getParserHandler( String systemId, boolean root ) {
   335         final Document dom = documentBuilder.newDocument();
   336         core.put( systemId, dom );
   337         if(root)
   338             rootDocuments.add(systemId);
   340         ContentHandler handler = getParserHandler(dom);
   342         // we will register the DOM to the map once the system ID becomes available.
   343         // but the SAX allows the event source to not to provide that information,
   344         // so be prepared for such case.
   345         HandlerImpl x = new HandlerImpl() {
   346             public Document getDocument() {
   347                 return dom;
   348             }
   349         };
   350         x.setContentHandler(handler);
   352         return x;
   353    }
   355     /**
   356      * Parses the given document and add it to the DOM forest.
   357      *
   358      * @return
   359      *      null if there was a parse error. otherwise non-null.
   360      */
   361     public Document parse( String systemId, InputSource inputSource, boolean root ) throws SAXException {
   362         Document dom = documentBuilder.newDocument();
   364         systemId = Options.normalizeSystemId(systemId);
   366         // put into the map before growing a tree, to
   367         // prevent recursive reference from causing infinite loop.
   368         core.put( systemId, dom );
   369         if(root)
   370             rootDocuments.add(systemId);
   372         try {
   373             XMLReader reader = parserFactory.newSAXParser().getXMLReader();
   374             reader.setContentHandler(getParserHandler(dom));
   375             if(errorReceiver!=null)
   376                 reader.setErrorHandler(errorReceiver);
   377             if(entityResolver!=null)
   378                 reader.setEntityResolver(entityResolver);
   379             reader.parse(inputSource);
   380         } catch( ParserConfigurationException e ) {
   381             // in practice, this exception won't happen.
   382             errorReceiver.error(e.getMessage(),e);
   383             core.remove(systemId);
   384             rootDocuments.remove(systemId);
   385             return null;
   386         } catch( IOException e ) {
   387             errorReceiver.error(Messages.format(Messages.DOMFOREST_INPUTSOURCE_IOEXCEPTION, systemId, e.toString()),e);
   388             core.remove(systemId);
   389             rootDocuments.remove(systemId);
   390             return null;
   391         }
   393         return dom;
   394     }
   396     public Document parse( String systemId, XMLStreamReader parser, boolean root ) throws XMLStreamException {
   397         Document dom = documentBuilder.newDocument();
   399         systemId = Options.normalizeSystemId(systemId);
   401         if(root)
   402             rootDocuments.add(systemId);
   404         if(systemId==null)
   405             throw new IllegalArgumentException("system id cannot be null");
   406         core.put( systemId, dom );
   408         new XMLStreamReaderToContentHandler(parser,getParserHandler(dom),false,false).bridge();
   410         return dom;
   411     }
   413     /**
   414      * Performs internalization.
   415      *
   416      * This method should be called only once, only after all the
   417      * schemas are parsed.
   418      *
   419      * @return
   420      *      the returned bindings need to be applied after schema
   421      *      components are built.
   422      */
   423     public SCDBasedBindingSet transform(boolean enableSCD) {
   424         return Internalizer.transform(this, enableSCD, options.disableXmlSecurity);
   425     }
   427     /**
   428      * Performs the schema correctness check by using JAXP 1.3.
   429      *
   430      * <p>
   431      * This is "weak", because {@link SchemaFactory#newSchema(Source[])}
   432      * doesn't handle inclusions very correctly (it ends up parsing it
   433      * from its original source, not in this tree), and because
   434      * it doesn't handle two documents for the same namespace very
   435      * well.
   436      *
   437      * <p>
   438      * We should eventually fix JAXP (and Xerces), but meanwhile
   439      * this weaker and potentially wrong correctness check is still
   440      * better than nothing when used inside JAX-WS (JAXB CLI and Ant
   441      * does a better job of checking this.)
   442      *
   443      * <p>
   444      * To receive errors, use {@link SchemaFactory#setErrorHandler(ErrorHandler)}.
   445      */
   446     public void weakSchemaCorrectnessCheck(SchemaFactory sf) {
   447         List<SAXSource> sources = new ArrayList<SAXSource>();
   448         for( String systemId : getRootDocuments() ) {
   449             Document dom = get(systemId);
   450             if (dom.getDocumentElement().getNamespaceURI().equals(Const.JAXB_NSURI))
   451                 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
   453             SAXSource ss = createSAXSource(systemId);
   454             try {
   455                 ss.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes",true);
   456             } catch (SAXException e) {
   457                 throw new AssertionError(e);    // Xerces wants this. See 6395322.
   458             }
   459             sources.add(ss);
   460         }
   462         try {
   463             allowExternalAccess(sf, "file,http", options.disableXmlSecurity).newSchema(sources.toArray(new SAXSource[0]));
   464         } catch (SAXException e) {
   465             // error should have been reported.
   466         } catch (RuntimeException re) {
   467             // JAXP RI isn't very trustworthy when it comes to schema error check,
   468             // and we know some cases where it just dies with NPE. So handle it gracefully.
   469             // this masks a bug in the JAXP RI, but we need a release that we have to make.
   470             try {
   471                 sf.getErrorHandler().warning(
   472                     new SAXParseException(Messages.format(
   473                         Messages.ERR_GENERAL_SCHEMA_CORRECTNESS_ERROR,re.getMessage()),
   474                         null,null,-1,-1,re));
   475             } catch (SAXException e) {
   476                 // ignore
   477             }
   478         }
   479     }
   481     /**
   482      * Creates a {@link SAXSource} that, when parsed, reads from this {@link DOMForest}
   483      * (instead of parsing the original source identified by the system ID.)
   484      */
   485     public @NotNull SAXSource createSAXSource(String systemId) {
   486         ContentHandlerNamespacePrefixAdapter reader = new ContentHandlerNamespacePrefixAdapter(new XMLFilterImpl() {
   487             // XMLReader that uses XMLParser to parse. We need to use XMLFilter to indrect
   488             // handlers, since SAX allows handlers to be changed while parsing.
   489             @Override
   490             public void parse(InputSource input) throws SAXException, IOException {
   491                 createParser().parse(input, this, this, this);
   492             }
   494             @Override
   495             public void parse(String systemId) throws SAXException, IOException {
   496                 parse(new InputSource(systemId));
   497             }
   498         });
   500         return new SAXSource(reader,new InputSource(systemId));
   501     }
   503     /**
   504      * Creates {@link XMLParser} for XSOM which reads documents from
   505      * this DOMForest rather than doing a fresh parse.
   506      *
   507      * The net effect is that XSOM will read transformed XML Schemas
   508      * instead of the original documents.
   509      */
   510     public XMLParser createParser() {
   511         return new DOMForestParser(this, new JAXPParser(XmlFactory.createParserFactory(options.disableXmlSecurity)));
   512     }
   514     public EntityResolver getEntityResolver() {
   515         return entityResolver;
   516     }
   518     public void setEntityResolver(EntityResolver entityResolver) {
   519         this.entityResolver = entityResolver;
   520     }
   522     public ErrorReceiver getErrorHandler() {
   523         return errorReceiver;
   524     }
   526     public void setErrorHandler(ErrorReceiver errorHandler) {
   527         this.errorReceiver = errorHandler;
   528     }
   530     /**
   531      * Gets all the parsed documents.
   532      */
   533     public Document[] listDocuments() {
   534         return core.values().toArray(new Document[core.size()]);
   535     }
   537     /**
   538      * Gets all the system IDs of the documents.
   539      */
   540     public String[] listSystemIDs() {
   541         return core.keySet().toArray(new String[core.keySet().size()]);
   542     }
   544     /**
   545      * Dumps the contents of the forest to the specified stream.
   546      *
   547      * This is a debug method. As such, error handling is sloppy.
   548      */
   549     @SuppressWarnings("CallToThreadDumpStack")
   550     public void dump( OutputStream out ) throws IOException {
   551         try {
   552             // create identity transformer
   553             boolean disableXmlSecurity = false;
   554             if (options != null) {
   555                 disableXmlSecurity = options.disableXmlSecurity;
   556             }
   557             TransformerFactory tf = XmlFactory.createTransformerFactory(disableXmlSecurity);
   558             Transformer it = tf.newTransformer();
   560             for (Map.Entry<String, Document> e : core.entrySet()) {
   561                 out.write( ("---<< "+e.getKey()+'\n').getBytes() );
   563                 DataWriter dw = new DataWriter(new OutputStreamWriter(out),null);
   564                 dw.setIndentStep("  ");
   565                 it.transform( new DOMSource(e.getValue()),
   566                     new SAXResult(dw));
   568                 out.write( "\n\n\n".getBytes() );
   569             }
   570         } catch( TransformerException e ) {
   571             e.printStackTrace();
   572         }
   573     }
   574 }

mercurial