/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.cocoon.transformation; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import javax.xml.transform.OutputKeys; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.caching.validity.EventValidity; import org.apache.cocoon.components.webdav.WebDAVEventFactory; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.xml.AttributesImpl; import org.apache.cocoon.xml.IncludeXMLConsumer; import org.apache.cocoon.xml.XMLUtils; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpState; import org.apache.commons.httpclient.HttpURL; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.source.impl.validity.AggregatedValidity; import org.apache.excalibur.xmlizer.XMLizer; import org.apache.webdav.lib.methods.HttpRequestBodyMethodBase; import org.w3c.dom.DocumentFragment; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * A general purpose, low level webdav transformer. Sends http requests defined in xml * directly to the server and returns the response to the processing stream. * * For a more high level approach, use WebDAVSource (GET/PUT/PROPPATCH) and DASLTransformer (SEARCH). * * * */ public class WebDAVTransformer extends AbstractSAXTransformer implements Disposable, CacheableProcessingComponent { // ---------------------------------------------------- Constants private static final String WEBDAV_SCHEME = "webdav://"; private static final String HTTP_SCHEME= "http://"; private static final String NS_URI = "http://cocoon.apache.org/webdav/1.0"; private static final String NS_PREFIX = "webdav:"; private static final String REQUEST_TAG = "request"; private static final String METHOD_ATTR = "method"; private static final String TARGET_ATTR = "target"; private static final String HEADER_TAG = "header"; private static final String NAME_ATTR = "name"; private static final String VALUE_ATTR = "value"; private static final String BODY_TAG = "body"; private static final String RESPONSE_TAG = "response"; private static final String STATUS_TAG = "status"; private static final String CODE_ATTR = "code"; private static final String MSG_ATTR = "msg"; private static HttpClient client = new HttpClient(new MultiThreadedHttpConnectionManager()); // ---------------------------------------------------- Member variables private HttpState m_state = null; private String m_method = null; private String m_target = null; private Map m_headers = null; private WebDAVEventFactory m_eventfactory = null; private DocumentFragment m_requestdocument = null; private AggregatedValidity m_validity = null; // ---------------------------------------------------- Lifecycle public WebDAVTransformer() { super.defaultNamespaceURI = "DAV:"; } public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, par); m_state = new HttpState(); if(null != par.getParameter("username", null)) { m_state.setCredentials(null, null, new UsernamePasswordCredentials( par.getParameter("username", ""), par.getParameter("password", "")) ); } if(m_eventfactory == null) { try { m_eventfactory = (WebDAVEventFactory)manager.lookup(WebDAVEventFactory.ROLE); } catch (ServiceException e) { // ignore, no eventcaching configured m_eventfactory = null; } } } /** * Helper method to do event caching * * @param methodurl The url to create the EventValidity for * @return an EventValidity object or null */ private SourceValidity makeWebdavEventValidity(HttpURL methodurl) { if (m_eventfactory == null) { return null; } SourceValidity evalidity = null; try { evalidity = new EventValidity(m_eventfactory.createEvent(methodurl)); if(getLogger().isDebugEnabled()) getLogger().debug("Created eventValidity for webdav request: "+evalidity); } catch (Exception e) { if(getLogger().isErrorEnabled()) getLogger().error("could not create EventValidity!",e); } return evalidity; } public void recycle() { super.recycle(); m_method = null; m_target = null; m_validity = null; m_requestdocument = null; } public void dispose() { recycle(); manager = null; } // ---------------------------------------------------- Transformer public void startElement(String uri, String name, String raw, Attributes atts) throws SAXException { if (name.equals(REQUEST_TAG) && uri.equals(NS_URI)) { m_headers = new HashMap(); if ((m_method = atts.getValue(METHOD_ATTR)) == null) { final String msg = "The <request> element must contain a \"method\" attribute"; throw new IllegalStateException(msg); } if ((m_target = atts.getValue(TARGET_ATTR)) == null) { throw new IllegalStateException("The <request> element must contain a \"target\" attribute"); } if (m_target.startsWith(WEBDAV_SCHEME)) { m_target = HTTP_SCHEME + m_target.substring(WEBDAV_SCHEME.length()); } else { throw new SAXException("Illegal value for target, must be an http:// or webdav:// URL"); } } else if (name.equals(HEADER_TAG) && uri.equals(NS_URI)) { final String hname = atts.getValue(NAME_ATTR); if (hname == null) { throw new SAXException("The <header> element requires a \"name\" attribute"); } final String value = atts.getValue(VALUE_ATTR); if (value == null) { throw new SAXException("The <header> element requires a \"value\" attribute"); } m_headers.put(hname, value); } else if (name.equals(BODY_TAG) && uri.equals(NS_URI)) { startRecording(); } else { super.startElement(uri, name, raw, atts); } } public void endElement(String uri, String name, String raw) throws SAXException { if (name.equals(REQUEST_TAG) && uri.equals(NS_URI)) { try { HttpURL url = new HttpURL(m_target); if(url.getUser() != null && !"".equals(url.getUser())) { m_state.setCredentials(null, new UsernamePasswordCredentials( url.getUser(), url.getPassword())); } m_target = url.getURI(); if (m_validity != null) { m_validity.add(makeWebdavEventValidity(url)); } } catch (Exception e) { //ignore } // create method WebDAVRequestMethod method = new WebDAVRequestMethod(m_target, m_method); try { // add request headers Iterator headers = m_headers.entrySet().iterator(); while (headers.hasNext()) { Map.Entry header = (Map.Entry) headers.next(); method.addRequestHeader((String) header.getKey(), (String) header.getValue()); } Properties props = XMLUtils.createPropertiesForXML(false); props.put(OutputKeys.ENCODING, "ISO-8859-1"); String body = XMLUtils.serializeNode(m_requestdocument, props); // set request body method.setRequestBody(body.getBytes("ISO-8859-1")); // execute the request executeRequest(method); } catch (ProcessingException e) { if(getLogger().isErrorEnabled()) { getLogger().debug("Couldn't read request from sax stream",e); } throw new SAXException("Couldn't read request from sax stream",e); } catch (UnsupportedEncodingException e) { if(getLogger().isErrorEnabled()) { getLogger().debug("ISO-8859-1 encoding not present",e); } throw new SAXException("ISO-8859-1 encoding not present",e); } finally { method.releaseConnection(); m_headers = null; } } else if (name.equals(HEADER_TAG) && uri.equals(NS_URI)) { // dont do anything } else if (name.equals(BODY_TAG) && uri.equals(NS_URI)) { m_requestdocument = super.endRecording(); } else { super.endElement(uri, name, raw); } } private void executeRequest(WebDAVRequestMethod method) throws SAXException { try { client.executeMethod(method.getHostConfiguration(), method, m_state); super.contentHandler.startPrefixMapping("webdav", NS_URI); // start <response> AttributesImpl atts = new AttributesImpl(); atts.addCDATAAttribute(TARGET_ATTR, m_target); atts.addCDATAAttribute(METHOD_ATTR, m_method); super.contentHandler.startElement(NS_URI, RESPONSE_TAG, NS_PREFIX + RESPONSE_TAG, atts); atts.clear(); // <status> atts.addCDATAAttribute(CODE_ATTR, String.valueOf(method.getStatusCode())); atts.addCDATAAttribute(MSG_ATTR, method.getStatusText()); super.contentHandler.startElement(NS_URI, STATUS_TAG, NS_PREFIX + STATUS_TAG, atts); atts.clear(); super.contentHandler.endElement(NS_URI, STATUS_TAG, NS_PREFIX + STATUS_TAG); // <header>s Header[] headers = method.getResponseHeaders(); for (int i = 0; i < headers.length; i++) { atts.addCDATAAttribute(NAME_ATTR, headers[i].getName()); atts.addCDATAAttribute(VALUE_ATTR, headers[i].getValue()); super.contentHandler.startElement(NS_URI, HEADER_TAG, NS_PREFIX + HEADER_TAG, atts); atts.clear(); super.contentHandler.endElement(NS_URI, HEADER_TAG, NS_PREFIX + HEADER_TAG); } // response <body> final InputStream in = method.getResponseBodyAsStream(); if (in != null) { String mimeType = null; Header header = method.getResponseHeader("Content-Type"); if (header != null) { mimeType = header.getValue(); int pos = mimeType.indexOf(';'); if (pos != -1) { mimeType = mimeType.substring(0, pos); } } if (mimeType != null && mimeType.equals("text/xml")) { super.contentHandler.startElement(NS_URI, BODY_TAG, NS_PREFIX + BODY_TAG, atts); IncludeXMLConsumer consumer = new IncludeXMLConsumer(super.contentHandler); XMLizer xmlizer = null; try { xmlizer = (XMLizer) manager.lookup(XMLizer.ROLE); xmlizer.toSAX(in, mimeType, m_target, consumer); } catch (ServiceException ce) { throw new SAXException("Missing service dependency: " + XMLizer.ROLE, ce); } finally { manager.release(xmlizer); } super.contentHandler.endElement(NS_URI, BODY_TAG, NS_PREFIX + BODY_TAG); } } // end <response> super.contentHandler.endElement(NS_URI, RESPONSE_TAG, NS_PREFIX + RESPONSE_TAG); super.contentHandler.endPrefixMapping(NS_URI); } catch (HttpException e) { throw new SAXException("Error executing WebDAV request." + " Server responded " + e.getReasonCode() + " (" + e.getReason() + ") - " + e.getMessage(), e); } catch (IOException e) { throw new SAXException("Error executing WebDAV request", e); } } // ---------------------------------------------------- CacheableProcessingComponent public Serializable getKey() { if (m_state == null) { return "WebDAVTransformer"; } final StringBuffer key = new StringBuffer(); // get the credentials final Credentials credentials = m_state.getCredentials(null, null); if (credentials != null) { if (credentials instanceof UsernamePasswordCredentials) { key.append(((UsernamePasswordCredentials) credentials).getUserName()); } else { key.append(credentials.toString()); } } return key.toString(); } public SourceValidity getValidity() { // dont do any caching when no event caching is set up if (m_eventfactory == null) { return null; } if (m_validity == null) { m_validity = new AggregatedValidity(); } return m_validity; } // ---------------------------------------------------- Implementation private static class WebDAVRequestMethod extends HttpRequestBodyMethodBase { private String m_name; private WebDAVRequestMethod(String uri, String name) { super(uri); m_name = name; } public String getName() { return m_name; } } }