| /* |
| * 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; |
| } |
| |
| } |
| |
| } |