001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.tools.ws.wsdl.parser;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.tools.ws.wscompile.ErrorReceiver;
041: import com.sun.tools.ws.wscompile.WsimportOptions;
042: import com.sun.tools.ws.wsdl.document.schema.SchemaConstants;
043: import com.sun.tools.xjc.reader.internalizer.LocatorTable;
044: import com.sun.xml.bind.marshaller.DataWriter;
045: import org.w3c.dom.Document;
046: import org.w3c.dom.Element;
047: import org.w3c.dom.NodeList;
048: import org.xml.sax.ContentHandler;
049: import org.xml.sax.InputSource;
050: import org.xml.sax.SAXException;
051: import org.xml.sax.XMLReader;
052: import org.xml.sax.helpers.XMLFilterImpl;
053:
054: import javax.xml.parsers.DocumentBuilder;
055: import javax.xml.parsers.DocumentBuilderFactory;
056: import javax.xml.parsers.ParserConfigurationException;
057: import javax.xml.parsers.SAXParserFactory;
058: import javax.xml.transform.Transformer;
059: import javax.xml.transform.TransformerException;
060: import javax.xml.transform.TransformerFactory;
061: import javax.xml.transform.dom.DOMSource;
062: import javax.xml.transform.sax.SAXResult;
063: import java.io.IOException;
064: import java.io.OutputStream;
065: import java.io.OutputStreamWriter;
066: import java.net.URI;
067: import java.net.URISyntaxException;
068: import java.util.*;
069:
070: /**
071: * @author Vivek Pandey
072: */
073: public class DOMForest {
074: /**
075: * To correctly feed documents to a schema parser, we need to remember
076: * which documents (of the forest) were given as the root
077: * documents, and which of them are read as included/imported
078: * documents.
079: * <p/>
080: * <p/>
081: * Set of system ids as strings.
082: */
083: protected final Set<String> rootDocuments = new HashSet<String>();
084:
085: /**
086: * Contains wsdl:import(s)
087: */
088: protected final Set<String> externalReferences = new HashSet<String>();
089:
090: /**
091: * actual data storage map<SystemId,Document>.
092: */
093: protected final Map<String, Document> core = new HashMap<String, Document>();
094: protected final WsimportOptions options;
095: protected final ErrorReceiver errorReceiver;
096:
097: private final DocumentBuilder documentBuilder;
098: private final SAXParserFactory parserFactory;
099:
100: /**
101: * inlined schema elements inside wsdl:type section
102: */
103: protected final List<Element> inlinedSchemaElements = new ArrayList<Element>();
104:
105: /**
106: * Stores location information for all the trees in this forest.
107: */
108: public final LocatorTable locatorTable = new LocatorTable();
109:
110: /**
111: * Stores all the outer-most <jaxb:bindings> customizations.
112: */
113: public final Set<Element> outerMostBindings = new HashSet<Element>();
114:
115: /**
116: * Schema language dependent part of the processing.
117: */
118: protected final InternalizationLogic logic;
119:
120: public DOMForest(InternalizationLogic logic,
121: WsimportOptions options, ErrorReceiver errReceiver) {
122: this .options = options;
123: this .errorReceiver = errReceiver;
124: this .logic = logic;
125: try {
126: DocumentBuilderFactory dbf = DocumentBuilderFactory
127: .newInstance();
128: dbf.setNamespaceAware(true);
129: this .documentBuilder = dbf.newDocumentBuilder();
130:
131: this .parserFactory = SAXParserFactory.newInstance();
132: this .parserFactory.setNamespaceAware(true);
133: } catch (ParserConfigurationException e) {
134: throw new AssertionError(e);
135: }
136: }
137:
138: public List<Element> getInlinedSchemaElement() {
139: return inlinedSchemaElements;
140: }
141:
142: public @NotNull
143: Document parse(InputSource source, boolean root)
144: throws SAXException, IOException {
145: if (source.getSystemId() == null)
146: throw new IllegalArgumentException();
147: return parse(source.getSystemId(), source, root);
148: }
149:
150: /**
151: * Parses an XML at the given location (
152: * and XMLs referenced by it) into DOM trees
153: * and stores them to this forest.
154: *
155: * @return the parsed DOM document object.
156: */
157: public Document parse(String systemId, boolean root)
158: throws SAXException, IOException {
159:
160: systemId = normalizeSystemId(systemId);
161:
162: InputSource is = null;
163:
164: // allow entity resolver to find the actual byte stream.
165: if (options.entityResolver != null)
166: is = options.entityResolver.resolveEntity(null, systemId);
167: if (is == null)
168: is = new InputSource(systemId);
169: else
170: systemId = is.getSystemId();
171:
172: if (core.containsKey(systemId)) {
173: // this document has already been parsed. Just ignore.
174: return core.get(systemId);
175: }
176:
177: if (!root)
178: addExternalReferences(systemId);
179:
180: // but we still use the original system Id as the key.
181: return parse(systemId, is, root);
182: }
183:
184: /**
185: * Parses the given document and add it to the DOM forest.
186: *
187: * @return null if there was a parse error. otherwise non-null.
188: */
189: public @NotNull
190: Document parse(String systemId, InputSource inputSource,
191: boolean root) throws SAXException, IOException {
192: Document dom = documentBuilder.newDocument();
193:
194: systemId = normalizeSystemId(systemId);
195:
196: // put into the map before growing a tree, to
197: // prevent recursive reference from causing infinite loop.
198: core.put(systemId, dom);
199:
200: dom.setDocumentURI(systemId);
201: if (root)
202: rootDocuments.add(systemId);
203:
204: try {
205: XMLReader reader = parserFactory.newSAXParser()
206: .getXMLReader();
207: reader.setContentHandler(getParserHandler(dom));
208: if (errorReceiver != null)
209: reader.setErrorHandler(errorReceiver);
210: if (options.entityResolver != null)
211: reader.setEntityResolver(options.entityResolver);
212: reader.parse(inputSource);
213: Element doc = dom.getDocumentElement();
214: if (doc == null) {
215: return null;
216: }
217: NodeList schemas = doc.getElementsByTagNameNS(
218: SchemaConstants.NS_XSD, "schema");
219: for (int i = 0; i < schemas.getLength(); i++) {
220: inlinedSchemaElements.add((Element) schemas.item(i));
221: }
222: } catch (ParserConfigurationException e) {
223: errorReceiver.error(e);
224: throw new SAXException(e.getMessage());
225: }
226:
227: return dom;
228: }
229:
230: public void addExternalReferences(String ref) {
231: if (!externalReferences.contains(ref))
232: externalReferences.add(ref);
233: }
234:
235: public Set<String> getExternalReferences() {
236: return externalReferences;
237: }
238:
239: public interface Handler extends ContentHandler {
240: /**
241: * Gets the DOM that was built.
242: */
243: public Document getDocument();
244: }
245:
246: private static abstract class HandlerImpl extends XMLFilterImpl
247: implements Handler {
248: }
249:
250: /**
251: * Returns a {@link ContentHandler} to feed SAX events into.
252: * <p/>
253: * The client of this class can feed SAX events into the handler
254: * to parse a document into this DOM forest.
255: */
256: public Handler getParserHandler(String systemId, boolean root) {
257: final Document dom = documentBuilder.newDocument();
258: core.put(systemId, dom);
259: if (root)
260: rootDocuments.add(systemId);
261:
262: ContentHandler handler = getParserHandler(dom);
263:
264: // we will register the DOM to the map once the system ID becomes available.
265: // but the SAX allows the event source to not to provide that information,
266: // so be prepared for such case.
267: HandlerImpl x = new HandlerImpl() {
268: public Document getDocument() {
269: return dom;
270: }
271: };
272: x.setContentHandler(handler);
273:
274: return x;
275: }
276:
277: /**
278: * Returns a {@link org.xml.sax.ContentHandler} to feed SAX events into.
279: * <p/>
280: * <p/>
281: * The client of this class can feed SAX events into the handler
282: * to parse a document into this DOM forest.
283: * <p/>
284: * This version requires that the DOM object to be created and registered
285: * to the map beforehand.
286: */
287: private ContentHandler getParserHandler(Document dom) {
288: ContentHandler handler = new DOMBuilder(dom, locatorTable,
289: outerMostBindings);
290: handler = new WhitespaceStripper(handler, errorReceiver,
291: options.entityResolver);
292: handler = new VersionChecker(handler, errorReceiver,
293: options.entityResolver);
294:
295: // insert the reference finder so that
296: // included/imported schemas will be also parsed
297: XMLFilterImpl f = logic.createExternalReferenceFinder(this );
298: f.setContentHandler(handler);
299:
300: if (errorReceiver != null)
301: f.setErrorHandler(errorReceiver);
302: if (options.entityResolver != null)
303: f.setEntityResolver(options.entityResolver);
304:
305: return f;
306: }
307:
308: private String normalizeSystemId(String systemId) {
309: try {
310: systemId = new URI(systemId).normalize().toString();
311: } catch (URISyntaxException e) {
312: // leave the system ID untouched. In my experience URI is often too strict
313: }
314: return systemId;
315: }
316:
317: boolean isExtensionMode() {
318: return options.isExtensionMode();
319: }
320:
321: /**
322: * Gets the DOM tree associated with the specified system ID,
323: * or null if none is found.
324: */
325: public Document get(String systemId) {
326: Document doc = core.get(systemId);
327:
328: if (doc == null && systemId.startsWith("file:/")
329: && !systemId.startsWith("file://")) {
330: // As of JDK1.4, java.net.URL.toExternal method returns URLs like
331: // "file:/abc/def/ghi" which is an incorrect file protocol URL according to RFC1738.
332: // Some other correctly functioning parts return the correct URLs ("file:///abc/def/ghi"),
333: // and this descripancy breaks DOM look up by system ID.
334:
335: // this extra check solves this problem.
336: doc = core.get("file://" + systemId.substring(5));
337: }
338:
339: if (doc == null && systemId.startsWith("file:")) {
340: // on Windows, filenames are case insensitive.
341: // perform case-insensitive search for improved user experience
342: String systemPath = getPath(systemId);
343: for (String key : core.keySet()) {
344: if (key.startsWith("file:")
345: && getPath(key).equalsIgnoreCase(systemPath)) {
346: doc = core.get(key);
347: break;
348: }
349: }
350: }
351:
352: return doc;
353: }
354:
355: /**
356: * Strips off the leading 'file:///' portion from an URL.
357: */
358: private String getPath(String key) {
359: key = key.substring(5); // skip 'file:'
360: while (key.length() > 0 && key.charAt(0) == '/')
361: key = key.substring(1);
362: return key;
363: }
364:
365: /**
366: * Gets all the system IDs of the documents.
367: */
368: public String[] listSystemIDs() {
369: return core.keySet().toArray(new String[core.keySet().size()]);
370: }
371:
372: /**
373: * Gets the system ID from which the given DOM is parsed.
374: * <p/>
375: * Poor-man's base URI.
376: */
377: public String getSystemId(Document dom) {
378: for (Map.Entry<String, Document> e : core.entrySet()) {
379: if (e.getValue() == dom)
380: return e.getKey();
381: }
382: return null;
383: }
384:
385: /**
386: * Gets the first one (which is more or less random) in {@link #rootDocuments}.
387: */
388: public String getFirstRootDocument() {
389: if (rootDocuments.isEmpty())
390: return null;
391: return rootDocuments.iterator().next();
392: }
393:
394: public Set<String> getRootDocuments() {
395: return rootDocuments;
396: }
397:
398: /**
399: * Dumps the contents of the forest to the specified stream.
400: * <p/>
401: * This is a debug method. As such, error handling is sloppy.
402: */
403: public void dump(OutputStream out) throws IOException {
404: try {
405: // create identity transformer
406: Transformer it = TransformerFactory.newInstance()
407: .newTransformer();
408:
409: for (Map.Entry<String, Document> e : core.entrySet()) {
410: out.write(("---<< " + e.getKey() + '\n').getBytes());
411:
412: DataWriter dw = new DataWriter(new OutputStreamWriter(
413: out), null);
414: dw.setIndentStep(" ");
415: it.transform(new DOMSource(e.getValue()),
416: new SAXResult(dw));
417:
418: out.write("\n\n\n".getBytes());
419: }
420: } catch (TransformerException e) {
421: e.printStackTrace();
422: }
423: }
424:
425: }
|