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.Serializable;
021: import java.util.Enumeration;
022: import java.util.Map;
023: import java.util.Properties;
024:
025: import javax.xml.transform.OutputKeys;
026:
027: import org.apache.avalon.framework.parameters.Parameters;
028: import org.apache.cocoon.ProcessingException;
029: import org.apache.cocoon.caching.CacheableProcessingComponent;
030: import org.apache.cocoon.caching.validity.EventValidity;
031: import org.apache.cocoon.components.webdav.WebDAVEventFactory;
032: import org.apache.cocoon.environment.SourceResolver;
033: import org.apache.cocoon.xml.XMLUtils;
034: import org.apache.cocoon.xml.dom.DOMStreamer;
035: import org.apache.commons.httpclient.HttpConnection;
036: import org.apache.commons.httpclient.HttpException;
037: import org.apache.commons.httpclient.HttpState;
038: import org.apache.commons.httpclient.HttpURL;
039: import org.apache.commons.httpclient.UsernamePasswordCredentials;
040: import org.apache.excalibur.source.SourceValidity;
041: import org.apache.excalibur.source.impl.validity.AggregatedValidity;
042: import org.apache.webdav.lib.BaseProperty;
043: import org.apache.webdav.lib.WebdavResource;
044: import org.apache.webdav.lib.methods.OptionsMethod;
045: import org.apache.webdav.lib.methods.SearchMethod;
046: import org.w3c.dom.DocumentFragment;
047: import org.w3c.dom.Element;
048: import org.xml.sax.Attributes;
049: import org.xml.sax.SAXException;
050: import org.xml.sax.helpers.AttributesImpl;
051:
052: /**
053: * This transformer performs DASL queries on DASL-enabled WebDAV servers.
054: * It expects a "query" element in the "http://cocoon.apache.org/webdav/dasl/1.0"
055: * namespace containing the DASL query to execute, with a "target" attribute specifiyng
056: * the webdav:// or http:// URL for the target WebDAV server. It will then replace
057: * it with a "query-result" element containing the WebDAV results.
058: *
059: * Each result will be contained in a "result" element with a "path" attribute pointing at it
060: * and all WebDAV properties presented as (namespaced) children elements.
061: *
062: * Sample invocation:
063: * lt;dasl:query xmlns:dasl="http://cocoon.apache.org/webdav/dasl/1.0"
064: * target="webdav://localhost/repos/"gt;
065: * lt;D:searchrequest xmlns:D="DAV:"gt;
066: * lt;D:basicsearchgt;
067: * lt;D:selectgt;
068: * lt;D:allprop/gt;
069: * lt;/D:selectgt;
070: * lt;D:fromgt;
071: * lt;D:scopegt;
072: * lt;D:hrefgt;/repos/lt;/D:hrefgt;
073: * lt;D:depthgt;infinitylt;/D:depthgt;
074: * lt;/D:scopegt;
075: * lt;/D:fromgt;
076: * lt;D:wheregt;
077: * lt;D:eqgt;
078: * lt;D:propgt;lt;D:getcontenttype/gt;lt;/D:propgt;
079: * lt;D:literalgt;text/htmllt;/D:literalgt;
080: * lt;/D:eqgt;
081: * lt;/D:wheregt;
082: * lt;D:orderbygt;
083: * lt;D:ordergt;
084: * lt;D:propgt;
085: * lt;D:getcontentlength/gt;
086: * lt;/D:propgt;
087: * lt;D:ascending/gt;
088: * lt;/D:ordergt;
089: * lt;D:ordergt;
090: * lt;D:propgt;
091: * lt;D:href/gt;
092: * lt;/D:propgt;
093: * lt;D:ascending/gt;
094: * lt;/D:ordergt;
095: * lt;/D:orderbygt;
096: * lt;/D:basicsearchgt;
097: * lt;/D:searchrequestgt;
098: * lt;/dasl:querygt;
099: *
100: * Features
101: * - Substitution of a value: with this feature it's possible to pass value from sitemap
102: * that are substituted into a query.
103: * sitemap example:
104: * lt;map:transformer type="dasl"gt;
105: * lt;parameter name="repos" value="/repos/"gt;
106: * lt;/map:transformergt;
107: * query example:
108: * ....
109: * lt;D:hrefgt;lt;substitute-value name="repos"/gt;lt;/D:hrefgt;
110: * ....
111: * This feature is like substitute-value of SQLTransformer
112: *
113: * TODO: the SWCL Search method doesn't preserve the result order, which makes
114: * order-by clauses useless.
115: *
116: * TODO: *much* better error handling.
117: *
118: * @author <a href="mailto: gianugo@apache.org">Gianugo Rabellino</a>
119: * @author <a href="mailto:d.madama@pro-netics.com>Daniele Madama</a>
120: * @version $Id: DASLTransformer.java 433543 2006-08-22 06:22:54Z crossley $
121: */
122: public class DASLTransformer extends AbstractSAXTransformer implements
123: CacheableProcessingComponent {
124:
125: /** The prefix for tag */
126: static final String PREFIX = "dasl";
127: /** The tag name identifying the query */
128: static final String QUERY_TAG = "query";
129: /** The tag namespace */
130: static final String DASL_QUERY_NS = "http://cocoon.apache.org/webdav/dasl/1.0";
131: /** The URL namespace */
132: static final String TARGET_URL = "target";
133: /** The WebDAV scheme*/
134: static final String WEBDAV_SCHEME = "webdav://";
135: /** The tag name of root_tag for result */
136: static final String RESULT_ROOT_TAG = "query-result";
137: /** The tag name of root_tag for errors */
138: static final String ERROR_ROOT_TAG = "error";
139: /** The tag name for substitution of query parameter */
140: static final String SUBSTITUTE_TAG = "substitute-value";
141: /** The tag name for substitution of query parameter */
142: static final String SUBSTITUTE_TAG_NAME_ATTRIBUTE = "name";
143:
144: protected static final String PATH_NODE_NAME = "path";
145: protected static final String RESOURCE_NODE_NAME = "resource";
146:
147: /** The target HTTP URL */
148: String targetUrl;
149:
150: /** The validity of this dasl transformation run */
151: private AggregatedValidity m_validity = null;
152:
153: /** The WebdavEventFactory to abstract Event creation */
154: private WebDAVEventFactory m_eventfactory = null;
155:
156: /**
157: * Intercept the <dasl:query> start tag.
158: *
159: * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
160: */
161: public void startElement(String uri, String name, String raw,
162: Attributes attr) throws SAXException {
163: if (name.equals(QUERY_TAG) && uri.equals(DASL_QUERY_NS)) {
164: this .startRecording();
165: if ((targetUrl = attr.getValue(TARGET_URL)) == null) {
166: throw new IllegalStateException(
167: "The query element must contain a \"target\" attribute");
168: }
169: //Sanityze target
170: if (targetUrl.startsWith(WEBDAV_SCHEME))
171: targetUrl = "http://"
172: + targetUrl.substring(WEBDAV_SCHEME.length());
173: if (!targetUrl.startsWith("http"))
174: throw new SAXException(
175: "Illegal value for target, must be an http:// or webdav:// URL");
176: } else if (name.equals(SUBSTITUTE_TAG)
177: && uri.equals(DASL_QUERY_NS)) {
178: String parName = attr.getValue(DASL_QUERY_NS,
179: SUBSTITUTE_TAG_NAME_ATTRIBUTE);
180: if (parName == null) {
181: throw new IllegalStateException(
182: "Substitute value elements must have a "
183: + SUBSTITUTE_TAG_NAME_ATTRIBUTE
184: + " attribute");
185: }
186: String substitute = this .parameters.getParameter(parName,
187: null);
188: if (getLogger().isDebugEnabled()) {
189: getLogger().debug("SUBSTITUTE VALUE " + substitute);
190: }
191: super .characters(substitute.toCharArray(), 0, substitute
192: .length());
193: } else {
194: super .startElement(uri, name, raw, attr);
195: }
196: }
197:
198: /**
199: * Intercept the <dasl:query> end tag, convert buffered input to a String, build and execute the
200: * DASL query.
201: *
202: * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
203: */
204: public void endElement(String uri, String name, String raw)
205: throws SAXException {
206: String query;
207: if (name.equals(QUERY_TAG) && uri.equals(DASL_QUERY_NS)) {
208: DocumentFragment frag = this .endRecording();
209: try {
210: Properties props = XMLUtils
211: .createPropertiesForXML(false);
212: props.put(OutputKeys.ENCODING, "ISO-8859-1");
213: query = XMLUtils.serializeNode(frag, props);
214: // Perform the DASL query
215: this .performSearchMethod(query);
216: } catch (ProcessingException e) {
217: throw new SAXException(
218: "Unable to fetch the query data:", e);
219: }
220: } else if (name.equals(SUBSTITUTE_TAG)
221: && uri.equals(DASL_QUERY_NS)) {
222: //Do nothing!!!!
223: } else {
224: super .endElement(uri, name, raw);
225: }
226: }
227:
228: protected void performSearchMethod(String query)
229: throws SAXException {
230: OptionsMethod optionsMethod = null;
231: SearchMethod searchMethod = null;
232: try {
233: DOMStreamer propertyStreamer = new DOMStreamer(
234: this .xmlConsumer);
235: optionsMethod = new OptionsMethod(this .targetUrl);
236: searchMethod = new SearchMethod(this .targetUrl, query);
237: HttpURL url = new HttpURL(this .targetUrl);
238: HttpState state = new HttpState();
239: state.setCredentials(null, new UsernamePasswordCredentials(
240: url.getUser(), url.getPassword()));
241: HttpConnection conn = new HttpConnection(url.getHost(), url
242: .getPort());
243:
244: // eventcaching stuff
245: SourceValidity extraValidity = makeWebdavEventValidity(url);
246: if (extraValidity != null && m_validity != null)
247: m_validity.add(extraValidity);
248: // end eventcaching stuff
249:
250: WebdavResource resource = new WebdavResource(new HttpURL(
251: this .targetUrl));
252: if (!resource.exists()) {
253: throw new SAXException(
254: "The WebDAV resource don't exist");
255: }
256: optionsMethod.execute(state, conn);
257: if (!optionsMethod.isAllowed("SEARCH")) {
258: throw new SAXException(
259: "The server doesn't support the SEARCH method");
260: }
261: int httpstatus = searchMethod.execute(state, conn);
262:
263: this .contentHandler.startElement(DASL_QUERY_NS,
264: RESULT_ROOT_TAG, PREFIX + ":" + RESULT_ROOT_TAG,
265: XMLUtils.EMPTY_ATTRIBUTES);
266:
267: // something might have gone wrong, report it
268: // 207 = multistatus webdav response
269: if (httpstatus != 207) {
270:
271: this .contentHandler.startElement(DASL_QUERY_NS,
272: ERROR_ROOT_TAG, PREFIX + ":" + ERROR_ROOT_TAG,
273: XMLUtils.EMPTY_ATTRIBUTES);
274:
275: // dump whatever the server said
276: propertyStreamer.stream(searchMethod
277: .getResponseDocument());
278:
279: this .contentHandler.endElement(DASL_QUERY_NS,
280: ERROR_ROOT_TAG, PREFIX + ":" + ERROR_ROOT_TAG);
281:
282: } else {
283: // show results
284:
285: Enumeration enumeration = searchMethod
286: .getAllResponseURLs();
287:
288: while (enumeration.hasMoreElements()) {
289: String path = (String) enumeration.nextElement();
290: Enumeration properties = searchMethod
291: .getResponseProperties(path);
292: AttributesImpl attr = new AttributesImpl();
293: attr.addAttribute(DASL_QUERY_NS, PATH_NODE_NAME,
294: PREFIX + ":" + PATH_NODE_NAME, "CDATA",
295: path);
296:
297: this .contentHandler.startElement(DASL_QUERY_NS,
298: RESOURCE_NODE_NAME, PREFIX + ":"
299: + RESOURCE_NODE_NAME, attr);
300: while (properties.hasMoreElements()) {
301: BaseProperty metadata = (BaseProperty) properties
302: .nextElement();
303: Element propertyElement = metadata.getElement();
304: propertyStreamer.stream(propertyElement);
305: }
306:
307: this .contentHandler.endElement(DASL_QUERY_NS,
308: RESOURCE_NODE_NAME, PREFIX + ":"
309: + RESOURCE_NODE_NAME);
310: }
311: }
312:
313: this .contentHandler.endElement(DASL_QUERY_NS,
314: RESULT_ROOT_TAG, PREFIX + ":" + RESULT_ROOT_TAG);
315: } catch (SAXException e) {
316: throw new SAXException("Unable to fetch the query data:", e);
317: } catch (HttpException e1) {
318: this .getLogger().error("Unable to contact Webdav server",
319: e1);
320: throw new SAXException("Unable to connect with server: ",
321: e1);
322: } catch (IOException e2) {
323: throw new SAXException("Unable to connect with server: ",
324: e2);
325: } catch (NullPointerException e) {
326: throw new SAXException("Unable to fetch the query data:", e);
327: } catch (Exception e) {
328: throw new SAXException("Generic Error:", e);
329: } finally {
330: // cleanup
331: if (searchMethod != null)
332: searchMethod.releaseConnection();
333: if (optionsMethod != null)
334: optionsMethod.releaseConnection();
335: }
336: }
337:
338: /**
339: * Helper method to do event caching
340: *
341: * @param methodurl The url to create the EventValidity for
342: * @return an EventValidity object or null
343: */
344: private SourceValidity makeWebdavEventValidity(HttpURL methodurl) {
345:
346: if (m_eventfactory == null) {
347: return null;
348: }
349:
350: SourceValidity evalidity = null;
351: try {
352:
353: evalidity = new EventValidity(m_eventfactory
354: .createEvent(methodurl));
355:
356: if (getLogger().isDebugEnabled())
357: getLogger().debug(
358: "Created eventValidity for dasl: " + evalidity);
359:
360: } catch (Exception e) {
361: if (getLogger().isErrorEnabled())
362: getLogger().error("could not create EventValidity!", e);
363: }
364: return evalidity;
365: }
366:
367: public void setup(SourceResolver resolver, Map objectModel,
368: String src, Parameters par) throws ProcessingException,
369: SAXException, IOException {
370: super .setup(resolver, objectModel, src, par);
371:
372: if (m_eventfactory == null) {
373: try {
374: m_eventfactory = (WebDAVEventFactory) manager
375: .lookup(WebDAVEventFactory.ROLE);
376: } catch (Exception e) {
377: if (getLogger().isErrorEnabled())
378: getLogger()
379: .error(
380: "Couldn't look up WebDAVEventFactory, event caching will not work!",
381: e);
382: }
383: }
384: }
385:
386: /**
387: * Forget about previous aggregated validity object
388: */
389: public void recycle() {
390: super .recycle();
391: m_validity = null;
392: }
393:
394: /**
395: * generates the cachekey, which is the classname plus any possible COCOON parameters
396: */
397: public Serializable getKey() {
398: if (this .parameters.getNames().length == 0) {
399: return getClass().getName();
400: } else {
401: StringBuffer buf = new StringBuffer();
402: buf.append(getClass().getName());
403:
404: // important for substitution
405: // we don't know yet which ones are relevant, so include all
406: String[] names = this .parameters.getNames();
407: for (int i = 0; i < names.length; i++) {
408: buf.append(";");
409: buf.append(names[i]);
410: buf.append("=");
411: try {
412: buf.append(this .parameters.getParameter(names[i]));
413: } catch (Exception e) {
414: if (getLogger().isErrorEnabled())
415: getLogger().error(
416: "Could not read parameter '" + names[i]
417: + "'!", e);
418: }
419: }
420:
421: return buf.toString();
422: }
423: }
424:
425: /**
426: * returns the validity which will be filled during processing of the requests
427: */
428: public SourceValidity getValidity() {
429: if (getLogger().isDebugEnabled())
430: getLogger().debug("getValidity() called!");
431:
432: // dont do any caching when no event caching is set up
433: if (m_eventfactory == null) {
434: return null;
435: }
436:
437: if (m_validity == null) {
438: m_validity = new AggregatedValidity();
439: }
440: return m_validity;
441: }
442:
443: }
|