001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.transformation;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.Serializable;
022: import java.io.UnsupportedEncodingException;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Properties;
027:
028: import javax.xml.transform.OutputKeys;
029:
030: import org.apache.avalon.framework.activity.Disposable;
031: import org.apache.avalon.framework.parameters.Parameters;
032: import org.apache.avalon.framework.service.ServiceException;
033: import org.apache.cocoon.ProcessingException;
034: import org.apache.cocoon.caching.CacheableProcessingComponent;
035: import org.apache.cocoon.caching.validity.EventValidity;
036: import org.apache.cocoon.components.webdav.WebDAVEventFactory;
037: import org.apache.cocoon.environment.SourceResolver;
038: import org.apache.cocoon.xml.AttributesImpl;
039: import org.apache.cocoon.xml.IncludeXMLConsumer;
040: import org.apache.cocoon.xml.XMLUtils;
041: import org.apache.commons.httpclient.Credentials;
042: import org.apache.commons.httpclient.Header;
043: import org.apache.commons.httpclient.HttpClient;
044: import org.apache.commons.httpclient.HttpException;
045: import org.apache.commons.httpclient.HttpState;
046: import org.apache.commons.httpclient.HttpURL;
047: import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
048: import org.apache.commons.httpclient.UsernamePasswordCredentials;
049: import org.apache.excalibur.source.SourceValidity;
050: import org.apache.excalibur.source.impl.validity.AggregatedValidity;
051: import org.apache.excalibur.xmlizer.XMLizer;
052: import org.apache.webdav.lib.methods.HttpRequestBodyMethodBase;
053: import org.w3c.dom.DocumentFragment;
054: import org.xml.sax.Attributes;
055: import org.xml.sax.SAXException;
056:
057: /**
058: * A general purpose, low level webdav transformer. Sends http requests defined in xml
059: * directly to the server and returns the response to the processing stream.
060: *
061: * For a more high level approach, use WebDAVSource (GET/PUT/PROPPATCH) and DASLTransformer (SEARCH).
062: *
063: *
064: *
065: */
066: public class WebDAVTransformer extends AbstractSAXTransformer implements
067: Disposable, CacheableProcessingComponent {
068:
069: // ---------------------------------------------------- Constants
070:
071: private static final String WEBDAV_SCHEME = "webdav://";
072: private static final String HTTP_SCHEME = "http://";
073:
074: private static final String NS_URI = "http://cocoon.apache.org/webdav/1.0";
075: private static final String NS_PREFIX = "webdav:";
076:
077: private static final String REQUEST_TAG = "request";
078: private static final String METHOD_ATTR = "method";
079: private static final String TARGET_ATTR = "target";
080:
081: private static final String HEADER_TAG = "header";
082: private static final String NAME_ATTR = "name";
083: private static final String VALUE_ATTR = "value";
084:
085: private static final String BODY_TAG = "body";
086:
087: private static final String RESPONSE_TAG = "response";
088: private static final String STATUS_TAG = "status";
089: private static final String CODE_ATTR = "code";
090: private static final String MSG_ATTR = "msg";
091:
092: private static HttpClient client = new HttpClient(
093: new MultiThreadedHttpConnectionManager());
094:
095: // ---------------------------------------------------- Member variables
096:
097: private HttpState m_state = null;
098:
099: private String m_method = null;
100: private String m_target = null;
101: private Map m_headers = null;
102:
103: private WebDAVEventFactory m_eventfactory = null;
104:
105: private DocumentFragment m_requestdocument = null;
106:
107: private AggregatedValidity m_validity = null;
108:
109: // ---------------------------------------------------- Lifecycle
110:
111: public WebDAVTransformer() {
112: super .defaultNamespaceURI = "DAV:";
113: }
114:
115: public void setup(SourceResolver resolver, Map objectModel,
116: String src, Parameters par) throws ProcessingException,
117: SAXException, IOException {
118: super .setup(resolver, objectModel, src, par);
119:
120: m_state = new HttpState();
121:
122: if (null != par.getParameter("username", null)) {
123: m_state.setCredentials(null, null,
124: new UsernamePasswordCredentials(par.getParameter(
125: "username", ""), par.getParameter(
126: "password", "")));
127: }
128:
129: if (m_eventfactory == null) {
130: try {
131: m_eventfactory = (WebDAVEventFactory) manager
132: .lookup(WebDAVEventFactory.ROLE);
133: } catch (ServiceException e) {
134: // ignore, no eventcaching configured
135: m_eventfactory = null;
136: }
137: }
138: }
139:
140: /**
141: * Helper method to do event caching
142: *
143: * @param methodurl The url to create the EventValidity for
144: * @return an EventValidity object or null
145: */
146: private SourceValidity makeWebdavEventValidity(HttpURL methodurl) {
147:
148: if (m_eventfactory == null) {
149: return null;
150: }
151:
152: SourceValidity evalidity = null;
153: try {
154:
155: evalidity = new EventValidity(m_eventfactory
156: .createEvent(methodurl));
157:
158: if (getLogger().isDebugEnabled())
159: getLogger().debug(
160: "Created eventValidity for webdav request: "
161: + evalidity);
162:
163: } catch (Exception e) {
164: if (getLogger().isErrorEnabled())
165: getLogger().error("could not create EventValidity!", e);
166: }
167: return evalidity;
168: }
169:
170: public void recycle() {
171: super .recycle();
172:
173: m_method = null;
174: m_target = null;
175: m_validity = null;
176: m_requestdocument = null;
177: }
178:
179: public void dispose() {
180: recycle();
181:
182: manager = null;
183: }
184:
185: // ---------------------------------------------------- Transformer
186:
187: public void startElement(String uri, String name, String raw,
188: Attributes atts) throws SAXException {
189: if (name.equals(REQUEST_TAG) && uri.equals(NS_URI)) {
190: m_headers = new HashMap();
191: if ((m_method = atts.getValue(METHOD_ATTR)) == null) {
192: final String msg = "The <request> element must contain a \"method\" attribute";
193: throw new IllegalStateException(msg);
194: }
195: if ((m_target = atts.getValue(TARGET_ATTR)) == null) {
196: throw new IllegalStateException(
197: "The <request> element must contain a \"target\" attribute");
198: }
199: if (m_target.startsWith(WEBDAV_SCHEME)) {
200: m_target = HTTP_SCHEME
201: + m_target.substring(WEBDAV_SCHEME.length());
202: } else {
203: throw new SAXException(
204: "Illegal value for target, must be an http:// or webdav:// URL");
205: }
206: } else if (name.equals(HEADER_TAG) && uri.equals(NS_URI)) {
207: final String hname = atts.getValue(NAME_ATTR);
208: if (hname == null) {
209: throw new SAXException(
210: "The <header> element requires a \"name\" attribute");
211: }
212: final String value = atts.getValue(VALUE_ATTR);
213: if (value == null) {
214: throw new SAXException(
215: "The <header> element requires a \"value\" attribute");
216: }
217: m_headers.put(hname, value);
218: } else if (name.equals(BODY_TAG) && uri.equals(NS_URI)) {
219: startRecording();
220: } else {
221: super .startElement(uri, name, raw, atts);
222: }
223: }
224:
225: public void endElement(String uri, String name, String raw)
226: throws SAXException {
227: if (name.equals(REQUEST_TAG) && uri.equals(NS_URI)) {
228:
229: try {
230: HttpURL url = new HttpURL(m_target);
231: if (url.getUser() != null && !"".equals(url.getUser())) {
232: m_state.setCredentials(null,
233: new UsernamePasswordCredentials(url
234: .getUser(), url.getPassword()));
235: }
236: m_target = url.getURI();
237:
238: if (m_validity != null) {
239: m_validity.add(makeWebdavEventValidity(url));
240: }
241:
242: } catch (Exception e) {
243: //ignore
244: }
245:
246: // create method
247: WebDAVRequestMethod method = new WebDAVRequestMethod(
248: m_target, m_method);
249:
250: try {
251: // add request headers
252: Iterator headers = m_headers.entrySet().iterator();
253: while (headers.hasNext()) {
254: Map.Entry header = (Map.Entry) headers.next();
255: method.addRequestHeader((String) header.getKey(),
256: (String) header.getValue());
257: }
258:
259: Properties props = XMLUtils
260: .createPropertiesForXML(false);
261: props.put(OutputKeys.ENCODING, "ISO-8859-1");
262: String body = XMLUtils.serializeNode(m_requestdocument,
263: props);
264: // set request body
265: method.setRequestBody(body.getBytes("ISO-8859-1"));
266:
267: // execute the request
268: executeRequest(method);
269: } catch (ProcessingException e) {
270: if (getLogger().isErrorEnabled()) {
271: getLogger().debug(
272: "Couldn't read request from sax stream", e);
273: }
274: throw new SAXException(
275: "Couldn't read request from sax stream", e);
276: } catch (UnsupportedEncodingException e) {
277: if (getLogger().isErrorEnabled()) {
278: getLogger().debug(
279: "ISO-8859-1 encoding not present", e);
280: }
281: throw new SAXException(
282: "ISO-8859-1 encoding not present", e);
283: } finally {
284: method.releaseConnection();
285: m_headers = null;
286: }
287: } else if (name.equals(HEADER_TAG) && uri.equals(NS_URI)) {
288: // dont do anything
289: } else if (name.equals(BODY_TAG) && uri.equals(NS_URI)) {
290: m_requestdocument = super .endRecording();
291: } else {
292: super .endElement(uri, name, raw);
293: }
294: }
295:
296: private void executeRequest(WebDAVRequestMethod method)
297: throws SAXException {
298: try {
299: client.executeMethod(method.getHostConfiguration(), method,
300: m_state);
301:
302: super .contentHandler.startPrefixMapping("webdav", NS_URI);
303:
304: // start <response>
305: AttributesImpl atts = new AttributesImpl();
306: atts.addCDATAAttribute(TARGET_ATTR, m_target);
307: atts.addCDATAAttribute(METHOD_ATTR, m_method);
308: super .contentHandler.startElement(NS_URI, RESPONSE_TAG,
309: NS_PREFIX + RESPONSE_TAG, atts);
310: atts.clear();
311:
312: // <status>
313: atts.addCDATAAttribute(CODE_ATTR, String.valueOf(method
314: .getStatusCode()));
315: atts.addCDATAAttribute(MSG_ATTR, method.getStatusText());
316: super .contentHandler.startElement(NS_URI, STATUS_TAG,
317: NS_PREFIX + STATUS_TAG, atts);
318: atts.clear();
319: super .contentHandler.endElement(NS_URI, STATUS_TAG,
320: NS_PREFIX + STATUS_TAG);
321:
322: // <header>s
323: Header[] headers = method.getResponseHeaders();
324: for (int i = 0; i < headers.length; i++) {
325: atts.addCDATAAttribute(NAME_ATTR, headers[i].getName());
326: atts.addCDATAAttribute(VALUE_ATTR, headers[i]
327: .getValue());
328: super .contentHandler.startElement(NS_URI, HEADER_TAG,
329: NS_PREFIX + HEADER_TAG, atts);
330: atts.clear();
331: super .contentHandler.endElement(NS_URI, HEADER_TAG,
332: NS_PREFIX + HEADER_TAG);
333: }
334:
335: // response <body>
336: final InputStream in = method.getResponseBodyAsStream();
337: if (in != null) {
338: String mimeType = null;
339: Header header = method
340: .getResponseHeader("Content-Type");
341: if (header != null) {
342: mimeType = header.getValue();
343: int pos = mimeType.indexOf(';');
344: if (pos != -1) {
345: mimeType = mimeType.substring(0, pos);
346: }
347: }
348: if (mimeType != null && mimeType.equals("text/xml")) {
349: super .contentHandler.startElement(NS_URI, BODY_TAG,
350: NS_PREFIX + BODY_TAG, atts);
351: IncludeXMLConsumer consumer = new IncludeXMLConsumer(
352: super .contentHandler);
353: XMLizer xmlizer = null;
354: try {
355: xmlizer = (XMLizer) manager
356: .lookup(XMLizer.ROLE);
357: xmlizer.toSAX(in, mimeType, m_target, consumer);
358: } catch (ServiceException ce) {
359: throw new SAXException(
360: "Missing service dependency: "
361: + XMLizer.ROLE, ce);
362: } finally {
363: manager.release(xmlizer);
364: }
365: super .contentHandler.endElement(NS_URI, BODY_TAG,
366: NS_PREFIX + BODY_TAG);
367: }
368: }
369:
370: // end <response>
371: super .contentHandler.endElement(NS_URI, RESPONSE_TAG,
372: NS_PREFIX + RESPONSE_TAG);
373:
374: super .contentHandler.endPrefixMapping(NS_URI);
375: } catch (HttpException e) {
376: throw new SAXException("Error executing WebDAV request."
377: + " Server responded " + e.getReasonCode() + " ("
378: + e.getReason() + ") - " + e.getMessage(), e);
379: } catch (IOException e) {
380: throw new SAXException("Error executing WebDAV request", e);
381: }
382: }
383:
384: // ---------------------------------------------------- CacheableProcessingComponent
385:
386: public Serializable getKey() {
387: if (m_state == null) {
388: return "WebDAVTransformer";
389: }
390: final StringBuffer key = new StringBuffer();
391: // get the credentials
392: final Credentials credentials = m_state.getCredentials(null,
393: null);
394: if (credentials != null) {
395: if (credentials instanceof UsernamePasswordCredentials) {
396: key.append(((UsernamePasswordCredentials) credentials)
397: .getUserName());
398: } else {
399: key.append(credentials.toString());
400: }
401: }
402: return key.toString();
403: }
404:
405: public SourceValidity getValidity() {
406:
407: // dont do any caching when no event caching is set up
408: if (m_eventfactory == null) {
409: return null;
410: }
411:
412: if (m_validity == null) {
413: m_validity = new AggregatedValidity();
414: }
415: return m_validity;
416: }
417:
418: // ---------------------------------------------------- Implementation
419:
420: private static class WebDAVRequestMethod extends
421: HttpRequestBodyMethodBase {
422:
423: private String m_name;
424:
425: private WebDAVRequestMethod(String uri, String name) {
426: super (uri);
427: m_name = name;
428: }
429:
430: public String getName() {
431: return m_name;
432: }
433:
434: }
435:
436: }
|