/******************************************************************************* * Copyright (c) 2004, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jst.jsp.core.internal.util; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.jst.jsp.core.internal.Logger; import org.eclipse.wst.sse.core.internal.util.JarUtilities; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.ibm.icu.util.StringTokenizer; /** * An XML Creator/Reader/Writer that 1) Ignores any DocumentType Nodes found * within the document (as well as any entities) 2) Ignores any * errors/exceptions from Xerces when loading a document 3) Can load Documents * from within a .JAR file (***read-only***) */ public class DocumentProvider { private Document document = null; private ErrorHandler errorHandler = null; private String fBaseReference; private String fileName = null; private boolean fValidating = false; private InputStream inputStream = null; private String jarFileName = null; private EntityResolver resolver = null; private Node rootElement = null; private String rootElementName = null; public DocumentProvider() { super(); } private String _getFileName() { if (inputStream != null) return null; else if (isJAR()) { return getJarFileName(); } return getFileName(); } private Document _getParsedDocumentDOM2() { Document result = null; InputStream is = null; try { DocumentBuilder builder = getDocumentBuilder(); // DOMParser parser = new DOMParser(); // parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", // false);//$NON-NLS-1$ // parser.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", // false);//$NON-NLS-1$ // parser.setErrorHandler(getNullErrorHandler()); // parser.setEntityResolver(getNullEntityResolver()); // is = getInputStream(); builder.setEntityResolver(getEntityResolver()); builder.setErrorHandler(getNullErrorHandler()); is = getInputStream(); if (is != null) result = builder.parse(is, getBaseReference()); } catch (SAXException e) { // parsing exception, notify the user? Logger.log(Logger.WARNING_DEBUG, "SAXException while reading descriptor " + _getFileName() + " " + e); //$NON-NLS-1$ //$NON-NLS-2$ } catch (FileNotFoundException e) { // NOT an "exceptional case"; do not Log } catch (IOException e) { Logger.log(Logger.WARNING_DEBUG, "IOException while reading descriptor " + _getFileName() + " " + e); //$NON-NLS-1$ //$NON-NLS-2$ } finally { if (is != null) { try { is.close(); } catch (Exception e) { // what can be done? } } } return result; } /** * @return */ public String getBaseReference() { return fBaseReference; } /** * * @return Document */ public Document getDocument() { return getDocument(true); } public Document getDocument(boolean createEmptyOnFailure) { if (document == null) load(createEmptyOnFailure); return document; } DocumentBuilder fDocumentBuilder = null; private DocumentBuilder getDocumentBuilder() { if (fDocumentBuilder == null) { fDocumentBuilder = CommonXML.getDocumentBuilder(isValidating()); } return fDocumentBuilder; } private DOMImplementation getDomImplementation() { DocumentBuilder builder = null; try { builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DOMImplementation impl = builder.getDOMImplementation(); return impl; } catch (ParserConfigurationException e1) { Logger.logException(e1); } catch (FactoryConfigurationError e1) { Logger.logException(e1); } return null; } /************************************************************************* * Takes a single string of the form "a/b/c" and ensures that that * structure exists below the head element, down through 'c', and returns * a <em>single</em> element 'c'. For multiple elements (such as * multiple <macro> elements contained within a single * <macros> element, full DOM access is required for searching and * child element manipulation. ************************************************************************/ public Element getElement(String name) { Element result = null; if (document == null) load(false); if (document != null) { result = (Element) getNode(getRootElement(), name); } return result; } /** * Returns an EntityResolver that won't try to load and resolve ANY * entities */ private EntityResolver getEntityResolver() { if (resolver == null) { resolver = new EntityResolver() { public InputSource resolveEntity(String publicID, String systemID) throws SAXException, IOException { InputSource result = null; if (isValidating()) { try { URL spec = new URL("file://" + getBaseReference()); //$NON-NLS-1$ URL url = new URL(spec, systemID); if (url.getProtocol().startsWith("file:")) { //$NON-NLS-1$ URLConnection connection = url.openConnection(); result = new InputSource(systemID != null ? systemID : "/_" + toString()); //$NON-NLS-1$ result.setPublicId(publicID); result.setByteStream(connection.getInputStream()); } } catch (Exception e) { result = null; } } if (result == null) { result = new InputSource(new ByteArrayInputStream(new byte[0])); result.setPublicId(publicID); result.setSystemId(systemID != null ? systemID : "/_" + getClass().getName()); //$NON-NLS-1$ } return result; } }; } return resolver; } /** * * @return java.lang.String */ public String getFileName() { return fileName; } /** * Returns and input stream to use as the source of the Document 1) from * an InputStream set on this instance 2) from a JAR file with the given * entry name 3) from a normal file * * @return InputStream */ public InputStream getInputStream() throws FileNotFoundException { if (inputStream != null) return inputStream; else if (isJAR()) { return JarUtilities.getInputStream(getJarFileName(), getFileName()); } else { return new BufferedInputStream(new FileInputStream(getFileName())); } } /** * * @return java.lang.String */ public String getJarFileName() { return jarFileName; } private Node getNamedChild(Node parent, String childName) { if (parent == null) { return null; } NodeList childList = parent.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { if (childList.item(i).getNodeName().equals(childName)) return childList.item(i); } return null; } private Document getNewDocument() { Document result = null; try { DOMImplementation domImplementation = getDomImplementation(); if (domImplementation != null) { result = domImplementation.createDocument("http://www.w3.org/XML/1998/namespace", getRootElementName(), null); //$NON-NLS-1$ NodeList children = result.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { result.removeChild(children.item(i)); } // we're going through this effort to avoid a NS element Element settings = result.createElementNS("http://www.w3.org/XML/1998/namespace", getRootElementName()); //$NON-NLS-1$ result.appendChild(settings); return result; } } catch (DOMException e) { Logger.logException(e); } return null; } /************************************************************************* * Takes a single string of the form "a/b/c" and ensures that that * structure exists below the head element, down through 'c', and returns * the element 'c'. ************************************************************************/ private Node getNode(Node node, String name) { StringTokenizer tokenizer = new StringTokenizer(name, "/"); //$NON-NLS-1$ String token = null; while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); if (getNamedChild(node, token) == null) node.appendChild(document.createElement(token)); node = getNamedChild(node, token); } return node; } /** * Returns an ErrorHandler that will not stop the parser on reported * errors */ private ErrorHandler getNullErrorHandler() { if (errorHandler == null) { errorHandler = new ErrorHandler() { public void error(SAXParseException exception) throws SAXException { Logger.log(Logger.WARNING_DEBUG, "SAXParseException with " + fBaseReference + "/" + getJarFileName() + "/" + getFileName() + " (error) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } public void fatalError(SAXParseException exception) throws SAXException { Logger.log(Logger.WARNING_DEBUG, "SAXParseException with " + fBaseReference + "/" + getJarFileName() + "/" + getFileName() + " (fatalError) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } public void warning(SAXParseException exception) throws SAXException { Logger.log(Logger.WARNING_DEBUG, "SAXParseException with " + fBaseReference + "/" + getJarFileName() + "/" + getFileName() + " (warning) while reading descriptor: " + exception.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }; } return errorHandler; } private Document getParsedDocument() { Document result = null; if (inputStream == null) { File existenceTester = null; if (isJAR()) existenceTester = new File(getJarFileName()); else existenceTester = new File(getFileName()); if (!existenceTester.exists()) return null; } result = _getParsedDocumentDOM2(); return result; } /** * Returns the root Element of the current document * * @return org.w3c.dom.Element */ public Node getRootElement() { return getRootElement(getDocument()); } /** * Returns the/a root Element for the current document * * @return org.w3c.dom.Element */ private Node getRootElement(Document doc) { if (doc == null) return null; if (doc.getDocumentElement() != null) return doc.getDocumentElement(); try { Element newRootElement = doc.createElement(getRootElementName()); doc.appendChild(newRootElement); return newRootElement; } catch (DOMException e) { Logger.logException(e); } return null; } /** * * @return java.lang.String */ public java.lang.String getRootElementName() { return rootElementName; } private boolean isJAR() { return getJarFileName() != null; } /** * @return */ public boolean isValidating() { return fValidating; } void load(boolean createEmptyOnFailure) { // rootElementName and fileName are expected to be defined at this // point document = getParsedDocument(); if (document != null) { if (rootElementName != null) rootElement = getRootElement(document); else rootElement = document.getDocumentElement(); } if (document == null || rootElement == null) { if (createEmptyOnFailure) { document = getNewDocument(); if (document != null) { NodeList children = document.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { if (children.item(i).getNodeType() == Node.ELEMENT_NODE && children.item(i).getNodeName().equals(getRootElementName())) rootElement = children.item(i); } if (rootElement == null) { for (int i = 0; i < children.getLength(); i++) { if (children.item(i).getNodeType() == Node.ELEMENT_NODE) { rootElement = children.item(i); break; } } } } } } } /** * @param string */ public void setBaseReference(String string) { fBaseReference = string; } /** * * @param newFileName * java.lang.String */ public void setFileName(java.lang.String newFileName) { fileName = newFileName; } /** * Sets the inputStream for which to provide a Document. * * @param inputStream * The inputStream to set */ public void setInputStream(InputStream iStream) { this.inputStream = iStream; } /** * * @param newJarFileName * java.lang.String */ public void setJarFileName(java.lang.String newJarFileName) { jarFileName = newJarFileName; } /** * * @param newRootElementName * java.lang.String */ public void setRootElementName(java.lang.String newRootElementName) { rootElementName = newRootElementName; } /** * @param b */ public void setValidating(boolean b) { fValidating = b; } }