Java tutorial
/* * $Header$ * $Revision: 208522 $ * $Date: 2005-02-21 23:13:08 +0800 (Mon, 21 Feb 2005) $ * * ==================================================================== * * Copyright 1999-2002 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.webdav.lib.methods; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.util.URIUtil; import org.apache.webdav.lib.Property; import org.apache.webdav.lib.ResponseEntity; import org.apache.webdav.lib.properties.PropertyFactory; import org.apache.webdav.lib.util.DOMUtils; import org.apache.webdav.lib.util.DOMWriter; import org.apache.webdav.lib.util.WebdavStatus; import org.apache.webdav.lib.util.XMLDebugOutputer; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * Utility class for XML response parsing. * */ public abstract class XMLResponseMethodBase extends HttpRequestBodyMethodBase { //static private final Log log = LogSource.getInstance(XMLResponseMethodBase.class.getName()); // debug level private int debug = 0; // ----------------------------------------------------------- Constructors /** * Method constructor. */ public XMLResponseMethodBase() { super(); } /** * Method constructor. * * @param uri the URI to request */ public XMLResponseMethodBase(String uri) { super(uri); } // ----------------------------------------------------- Instance Variables /** * XML Debug Outputer */ private XMLDebugOutputer xo = new XMLDebugOutputer(); /** * Response document. */ private Document responseDocument = null; /** * Document builder. */ protected DocumentBuilder builder = null; /** * Hashtable of response nodes */ private Hashtable responseHashtable = null; /** * Vector of response nodes, to keep track of insertion order * FIXME: the above Hashtable and this Vector should be ported * to plain Collections */ protected Vector responseURLs = null; protected String decodeResponseHrefs = null; protected boolean assertHrefsArePathes = false; // ------------------------------------------------------------- Properties /** * Response document getter. * * @return Document response document */ public Document getResponseDocument() { return this.responseDocument; } /** * Return an enumeration containing the responses. * * @return An enumeration containing objects implementing the * ResponseEntity interface */ public Enumeration getResponses() { return getResponseHashtable().elements(); } // --------------------------------------------------- WebdavMethod Methods /** * Debug property setter. * * @param int Debug */ public void setDebug(int debug) { this.debug = debug; xo.setDebug((debug > 0)); } /** * Debug property getter. */ public int getDebug() { return this.debug; } /** * Sets whether the href in responses are decoded, as early as possible. * The <code>href</code> data in responses is often url-encoded, but not * alwyas in a comparable way. Set this to a non-null value to decode the * hrefs as early as possible. * @param encoding The encoding used in while decoding (UTF-8 is recommended) */ public void setDecodeResponseHrefs(String encoding) { this.decodeResponseHrefs = encoding; } /** * Determines whether hrefs of response elements are normalized to * be pathes only. * * <p>If set to <code>true</code> schema, host, port and other infos * before the URL path are removed. */ public void setAssertHrefsArePathes(boolean value) { this.assertHrefsArePathes = value; } /** * Reset the State of the class to its initial state, so that it can be * used again. */ public void recycle() { super.recycle(); responseHashtable = null; responseURLs = null; } protected void readResponseBody(HttpState state, HttpConnection conn) throws IOException, HttpException { super.readResponseBody(state, conn); InputStream inStream = getResponseBodyAsStream(); if (inStream != null) { parseResponse(inStream, state, conn); inStream.close(); } } /** * Return the length (in bytes) of my request body, suitable for use in a * <tt>Content-Length</tt> header. * * <p> * Return <tt>-1</tt> when the content-length is unknown. * </p> * * <p> * This implementation returns <tt>0</tt>, indicating that the request has * no body. * </p> * * @return <tt>0</tt>, indicating that the request has no body. */ protected int getRequestContentLength() { if (!isRequestContentAlreadySet()) { String contents = generateRequestBody(); // be nice - allow overriding functions to return null or empty // strings for no content. if (contents == null) contents = ""; setRequestBody(contents); if (debug > 0) { System.out.println("\n>>>>>>> to server ---------------------------------------------------"); System.out.println(getName() + " " + getPath() + (getQueryString() != null ? "?" + getQueryString() : "") + " " + "HTTP/1.1"); Header[] headers = getRequestHeaders(); for (int i = 0; i < headers.length; i++) { Header header = headers[i]; System.out.print(header.toString()); } System.out.println("Content-Length: " + super.getRequestContentLength()); if (this instanceof DepthSupport) { System.out.println("Depth: " + ((DepthSupport) this).getDepth()); } System.out.println(); xo.print(contents); System.out.println("------------------------------------------------------------------------"); } } return super.getRequestContentLength(); } /** * DAV requests that contain a body must override this function to * generate that body. * * <p>The default behavior simply returns an empty body.</p> */ protected String generateRequestBody() { return ""; } /** * Write the request body to the given {@link HttpConnection}. * * <p> * This implementation writes any computed body and returns <tt>true</tt>. * </p> * * @param state the client state * @param conn the connection to write to * * @return <tt>true</tt> * @throws IOException when i/o errors occur reading the response * @throws HttpException when a protocol error occurs or state is invalid */ protected boolean writeRequestBody(HttpState state, HttpConnection conn) throws IOException, HttpException { if (getRequestContentLength() > 0) { return super.writeRequestBody(state, conn); } return true; } /** * Parse response. * * @param input Input stream */ public void parseResponse(InputStream input, HttpState state, HttpConnection conn) throws IOException, HttpException { // Also accept OK sent by buggy servers in reply to a PROPFIND or // REPORT (Xythos, Catacomb, ...?). if (getStatusCode() == WebdavStatus.SC_MULTI_STATUS || (this instanceof PropFindMethod || this instanceof ReportMethod)) { try { parseXMLResponse(input); } catch (IOException e) { // FIX ME: provide a way to deliver non xml data } } } protected void parseXMLResponse(InputStream input) throws IOException, HttpException { if (builder == null) { try { // TODO: avoid the newInstance call for each method instance for performance reasons. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new HttpException("XML Parser Configuration error: " + e.getMessage()); } } try { // avoid ugly printlns from the default error handler. builder.setErrorHandler(new DummyErrorHandler()); responseDocument = builder.parse(new InputSource(input)); if (debug > 0) { System.out.println("\n<<<<<<< from server ---------------------------------------------------"); System.out.println(getStatusLine()); Header[] headers = getResponseHeaders(); for (int i = 0; i < headers.length; i++) { Header header = headers[i]; System.out.print(header.toString()); } System.out.println(); xo.print(responseDocument); System.out.println("------------------------------------------------------------------------"); } } catch (Exception e) { throw new IOException("XML parsing error; response stream is not valid XML: " + e.getMessage()); } // init the response table to display the responses during debugging /*if (debug > 10) { //if (log.isDebugEnabled()) { initResponseHashtable(); }*/ } protected Hashtable getResponseHashtable() { checkUsed(); if (responseHashtable == null) { initResponseHashtable(); } return responseHashtable; } protected Vector getResponseURLs() { checkUsed(); if (responseHashtable == null) { initResponseHashtable(); } return responseURLs; } private synchronized void initResponseHashtable() { if (responseHashtable == null) { responseHashtable = new Hashtable(); responseURLs = new Vector(); int status = getStatusLine().getStatusCode(); // Also accept OK sent by buggy servers in reply to a PROPFIND // or REPORT (Xythos, Catacomb, ...?). if (status == WebdavStatus.SC_MULTI_STATUS || (this instanceof PropFindMethod || this instanceof ReportMethod) && status == HttpStatus.SC_OK) { Document rdoc = getResponseDocument(); NodeList list = null; if (rdoc != null) { Element multistatus = getResponseDocument().getDocumentElement(); list = multistatus.getChildNodes(); } if (list != null) { for (int i = 0; i < list.getLength(); i++) { try { Element child = (Element) list.item(i); String name = DOMUtils.getElementLocalName(child); String namespace = DOMUtils.getElementNamespaceURI(child); if (Response.TAG_NAME.equals(name) && "DAV:".equals(namespace)) { Response response = new ResponseWithinMultistatus(child); String href = getHref(response); responseHashtable.put(href, response); responseURLs.add(href); } } catch (ClassCastException e) { } } } } else if (responseDocument != null) { Response response = new SingleResponse(responseDocument, getPath(), status); String href = getHref(response); responseHashtable.put(href, response); responseURLs.add(href); } } } private String getHref(Response response) { String href = response.getHref(); if (this.decodeResponseHrefs != null) { try { href = URIUtil.decode(href, this.decodeResponseHrefs); } catch (URIException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (this.assertHrefsArePathes) { int pos = href.indexOf("://"); if (pos != -1) { pos = href.indexOf('/', pos + 3); if (pos != -1) { href = href.substring(pos); } else { href = "/"; } } } return href; } /** * This method creates a property implementation from an element. * It treats known properties (i.e., the DAV properties) specially. * These properties are instantiated as an implementation from the * <code>org.apache.webdav.lib.properties</code> package. */ protected static Property convertElementToProperty(Response response, Element element) { return PropertyFactory.create(response, element); } // ---------------------------------------------------------- Inner Classes /** * An abstract class that models a DAV:response. */ public abstract class Response implements ResponseEntity { protected Node node = null; Response(Node node) { this.node = node; } public static final String TAG_NAME = "response"; public abstract int getStatusCode(); public abstract String getHref(); public Enumeration getHistories() { Vector result = new Vector(); return result.elements(); } public Enumeration getWorkspaces() { Vector result = new Vector(); return result.elements(); } public Enumeration getProperties() { NodeList list = DOMUtils.getElementsByTagNameNS(node, "prop", "DAV:"); Vector vector = new Vector(); for (int i = 0; list != null && i < list.getLength(); i++) { Element element = (Element) list.item(i); NodeList children = element.getChildNodes(); for (int j = 0; children != null && j < children.getLength(); j++) { try { Element child = (Element) children.item(j); vector.addElement(XMLResponseMethodBase.convertElementToProperty(this, child)); } catch (ClassCastException e) { } } } return vector.elements(); } public String toString() { StringWriter tmp = new StringWriter(); DOMWriter domWriter = new DOMWriter(tmp, true); domWriter.print(node); return tmp.getBuffer().toString(); } } /** * A class that models the DAV:response element within a multistatus. */ class ResponseWithinMultistatus extends Response { public ResponseWithinMultistatus(Element element) { super(element); } public int getStatusCode() { // The status element for the response can be inside the propstat element // or directly inside the response element. // <multistatus xmlns=\DAV:\> // <response> // <href>/slide/files/</href> // <propstat> // <prop><displayname>files</displayname></prop> // <status>HTTP/1.1 200 OK</status> // </propstat> // </response> // </multistatus> Element propstat = getFirstElement("DAV:", "propstat"); if (propstat != null) { Element status = DOMUtils.getFirstElement(propstat, "DAV:", "status"); if (status != null) { return DOMUtils.parseStatus(DOMUtils.getTextValue(status)); } } // <multistatus xmlns=\DAV:\> // <response> // <href>/slide/files/</href> // <href>/slide/files/a</href> // <status>HTTP/1.1 200 OK</status> // </response> // </multistatus> Element status = getFirstElement("DAV:", "status"); if (status != null) { return DOMUtils.parseStatus(DOMUtils.getTextValue(status)); } return -1; } public String getHref() { Element href = getFirstElement("DAV:", "href"); if (href != null) { return DOMUtils.getTextValue(href); } else { return ""; } } protected Element getFirstElement(String namespace, String name) { return DOMUtils.getFirstElement(this.node, namespace, name); } } class SingleResponse extends Response { private int statusCode = -1; private String href = null; SingleResponse(Document document, String href, int statusCode) { super(document); this.statusCode = statusCode; this.href = href; } public int getStatusCode() { return this.statusCode; } public String getHref() { return this.href; } } class OptionsResponse extends SingleResponse { OptionsResponse(Document document, String href, int statusCode) { super(document, href, statusCode); } public Enumeration getWorkspaces() { Node root = responseDocument.cloneNode(true).getFirstChild(); //System.out.println("Rootnode ws: "+ root.getNodeName()); String sPrefix = root.getPrefix(); Vector result = new Vector(); Node child = root.getFirstChild(); while (child != null && !child.getNodeName().equalsIgnoreCase(sPrefix + ":workspace-collection-set")) { child = child.getNextSibling(); } if (child != null && child.getNodeName().equalsIgnoreCase(sPrefix + ":workspace-collection-set")) { child = child.getFirstChild(); while (child != null) { result.add(child.getFirstChild().getNodeValue()); child = child.getNextSibling(); } } return result.elements(); } public Enumeration getHistories() { Node root = responseDocument.cloneNode(true).getFirstChild(); //System.out.println("Rootnode vh : " + root.getNodeName()); String sPrefix = root.getPrefix(); Vector result = new Vector(); //System.out.println("Prefix : " + sPrefix); Node child = root.getFirstChild(); while (child != null && !child.getNodeName().equalsIgnoreCase(sPrefix + ":version-history-collection-set")) { child = child.getNextSibling(); } if (child != null && child.getNodeName().equalsIgnoreCase(sPrefix + ":version-history-collection-set")) { child = child.getFirstChild(); while (child != null) { result.add(child.getFirstChild().getNodeValue()); child = child.getNextSibling(); } } return result.elements(); } } protected void setDocument(Document doc) { responseDocument = doc; } protected void setResponseHashtable(Hashtable h) { responseHashtable = h; } private static class DummyErrorHandler implements ErrorHandler { /** * Receive notification of a warning. * * <p>SAX parsers will use this method to report conditions that * are not errors or fatal errors as defined by the XML 1.0 * recommendation. The default behaviour is to take no action.</p> * * <p>The SAX parser must continue to provide normal parsing events * after invoking this method: it should still be possible for the * application to process the document through to the end.</p> * * <p>Filters may use this method to report other, non-XML warnings * as well.</p> * * @param exception The warning information encapsulated in a * SAX parse exception. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see org.xml.sax.SAXParseException */ public void warning(SAXParseException exception) throws SAXException { // System.out.println("warning: " + exception.getMessage()); } /** * Receive notification of a recoverable error. * * <p>This corresponds to the definition of "error" in section 1.2 * of the W3C XML 1.0 Recommendation. For example, a validating * parser would use this callback to report the violation of a * validity constraint. The default behaviour is to take no * action.</p> * * <p>The SAX parser must continue to provide normal parsing events * after invoking this method: it should still be possible for the * application to process the document through to the end. If the * application cannot do so, then the parser should report a fatal * error even if the XML 1.0 recommendation does not require it to * do so.</p> * * <p>Filters may use this method to report other, non-XML errors * as well.</p> * * @param exception The error information encapsulated in a * SAX parse exception. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see org.xml.sax.SAXParseException */ public void error(SAXParseException exception) throws SAXException { // System.out.println("error: " + exception.getMessage()); } /** * Receive notification of a non-recoverable error. * * <p>This corresponds to the definition of "fatal error" in * section 1.2 of the W3C XML 1.0 Recommendation. For example, a * parser would use this callback to report the violation of a * well-formedness constraint.</p> * * <p>The application must assume that the document is unusable * after the parser has invoked this method, and should continue * (if at all) only for the sake of collecting addition error * messages: in fact, SAX parsers are free to stop reporting any * other events once this method has been invoked.</p> * * @param exception The error information encapsulated in a * SAX parse exception. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see org.xml.sax.SAXParseException */ public void fatalError(SAXParseException exception) throws SAXException { // System.out.println("fatal: " + exception.getMessage()); } } }