001: /*
002: * Copyright 2005 Hippo Webworks.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package nl.hippo.slide.webdav.method;
017:
018: import java.io.IOException;
019: import java.text.ParseException;
020: import java.util.BitSet;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import org.apache.lucene.index.IndexReader;
025: import org.apache.lucene.index.Term;
026: import org.apache.lucene.index.TermDocs;
027: import org.apache.lucene.index.TermEnum;
028: import org.apache.lucene.search.Query;
029: import org.apache.lucene.search.QueryFilter;
030: import org.apache.slide.common.NamespaceAccessToken;
031: import org.apache.slide.common.RequestedProperties;
032: import org.apache.slide.common.RequestedProperty;
033: import org.apache.slide.common.SlideException;
034: import org.apache.slide.index.lucene.Index;
035: import org.apache.slide.index.lucene.IndexConfiguration;
036: import org.apache.slide.index.lucene.expressions.AbstractLuceneExpression;
037: import org.apache.slide.search.BadGatewayException;
038: import org.apache.slide.search.BadQueryException;
039: import org.apache.slide.search.InvalidQueryException;
040: import org.apache.slide.search.InvalidScopeException;
041: import org.apache.slide.search.QueryScope;
042: import org.apache.slide.search.RequestedResource;
043: import org.apache.slide.search.Search;
044: import org.apache.slide.search.SearchException;
045: import org.apache.slide.search.SearchQuery;
046: import org.apache.slide.search.SearchQueryResult;
047: import org.apache.slide.search.basic.IBasicQuery;
048: import org.apache.slide.security.AccessDeniedException;
049: import org.apache.slide.structure.ObjectNotFoundException;
050: import org.apache.slide.util.Configuration;
051: import org.apache.slide.webdav.WebdavException;
052: import org.apache.slide.webdav.WebdavServletConfig;
053: import org.apache.slide.webdav.method.AbstractWebdavMethod;
054: import org.apache.slide.webdav.method.ReadMethod;
055: import org.apache.slide.webdav.util.ComputedPropertyProvider;
056: import org.apache.slide.webdav.util.PropertyRetriever;
057: import org.apache.slide.webdav.util.WebdavConstants;
058: import org.apache.slide.webdav.util.WebdavStatus;
059: import org.apache.slide.webdav.util.WebdavUtils;
060: import org.jdom.Document;
061: import org.jdom.Element;
062: import org.jdom.JDOMException;
063: import org.jdom.Namespace;
064: import org.jdom.output.XMLOutputter;
065:
066: /**
067: * FACETS method.
068: *
069: */
070: public class FacetsMethod extends AbstractWebdavMethod implements
071: WebdavConstants, ReadMethod {
072:
073: public static final Namespace HIPPO_NS = Namespace.getNamespace(
074: "hippo", "http://hippo.nl/slide");
075:
076: // ----------------------------------------------------- Instance variables
077:
078: private SearchQuery searchQuery = null;
079: private Search searchHelper = null;
080:
081: private RequestedProperties requestedProperties = null;
082:
083: /** if true, an ALL_PROP request will include computed props */
084: protected boolean extendedAllprop = false;
085:
086: // ----------------------------------------------------------- Constructors
087:
088: /**
089: * Constructor.
090: *
091: * @param token the token for accessing the namespace
092: * @param config configuration of the WebDAV servlet
093: */
094: public FacetsMethod(NamespaceAccessToken token,
095: WebdavServletConfig config) {
096: super (token, config);
097: }
098:
099: /**
100: * Method parseRequest
101: *
102: * @throws WebdavException
103: */
104: protected void parseRequest() throws WebdavException {
105:
106: searchHelper = token.getSearchHelper();
107: String slidePath = null;
108:
109: extendedAllprop = getBooleanInitParameter("extendedAllprop");
110:
111: if (Configuration.useSearch()) {
112: try {
113: Element queryElement = getQueryElement();
114: String grammarNamespace = queryElement
115: .getNamespaceURI();
116:
117: // SearchLanguage language = searchHelper.getLanguage (grammarNamespace);
118: int maxDepth = getConfig().getDepthLimit();
119:
120: searchQuery = searchHelper.createSearchQuery(
121: grammarNamespace, queryElement, slideToken,
122: maxDepth, new ComputedPropertyProvider(token,
123: slideToken, getSlideContextPath(),
124: getConfig()), req.getRequestURI());
125:
126: requestedProperties = searchQuery.requestedProperties();
127:
128: if (searchQuery instanceof IBasicQuery) {
129: QueryScope scope = ((IBasicQuery) searchQuery)
130: .getScope();
131: slidePath = ((IBasicQuery) searchQuery)
132: .getSlidePath();
133:
134: // check, if scope is accessible (ACL)
135: token.getContentHelper().retrieve(slideToken,
136: slidePath);
137:
138: scope.setIsCollection(WebdavUtils.isCollection(
139: token, slideToken, slidePath));
140: }
141: } catch (JDOMException e) {
142: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
143: resp.setContentType(TEXT_XML_UTF_8);
144: createErrorResult(SearchQueryResult.STATUS_BAD_QUERY, e
145: .getMessage());
146: throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
147: } catch (InvalidQueryException e) {
148: resp.setStatus(WebdavStatus.SC_UNPROCESSABLE_ENTITY);
149: resp.setContentType(TEXT_XML_UTF_8);
150: createErrorResult(
151: SearchQueryResult.STATUS_UNPROCESSABLE_ENTITY,
152: e.getMessage());
153: throw new WebdavException(
154: WebdavStatus.SC_UNPROCESSABLE_ENTITY);
155: } catch (BadGatewayException e) {
156: resp.setStatus(WebdavStatus.SC_BAD_GATEWAY);
157: resp.setContentType(TEXT_XML_UTF_8);
158: createErrorResult(SearchQueryResult.STATUS_BAD_GATEWAY,
159: e.getMessage());
160: throw new WebdavException(WebdavStatus.SC_BAD_GATEWAY);
161: } catch (InvalidScopeException e) {
162: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
163: resp.setContentType(TEXT_XML_UTF_8);
164: createErrorResult(
165: SearchQueryResult.STATUS_INVALID_SCOPE, e
166: .getMessage());
167:
168: throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
169: } catch (BadQueryException e) {
170: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
171: resp.setContentType(TEXT_XML_UTF_8);
172: createErrorResult(SearchQueryResult.STATUS_BAD_QUERY, e
173: .getMessage());
174: throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
175: } catch (AccessDeniedException e) {
176:
177: String contextPath = getSlideContextPath();
178: AccessDeniedException ade = new AccessDeniedException(
179: contextPath + e.getObjectUri(), contextPath
180: + e.getSubjectUri(), contextPath
181: + e.getActionUri());
182:
183: String msg = ade.getMessage();
184: resp.setStatus(WebdavStatus.SC_FORBIDDEN);
185: resp.setContentType(TEXT_XML_UTF_8);
186: createErrorResult(SearchQueryResult.STATUS_FORBIDDEN,
187: msg);
188: throw new WebdavException(WebdavStatus.SC_FORBIDDEN);
189: } catch (ObjectNotFoundException e) {
190: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
191: resp.setContentType(TEXT_XML_UTF_8);
192: createErrorResult(
193: SearchQueryResult.STATUS_INVALID_SCOPE,
194: "scope " + slidePath + " is invalid");
195:
196: throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
197: } catch (SlideException e) {
198: e.printStackTrace();
199: }
200: } else {
201: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
202: resp.setContentType(TEXT_XML_UTF_8);
203: createErrorResult(SearchQueryResult.STATUS_BAD_QUERY,
204: "FACETS not implemented on this server");
205: throw new WebdavException(WebdavStatus.SC_BAD_REQUEST);
206: }
207: }
208:
209: protected int hitsForTermInQuery(IndexReader reader,
210: BitSet matchingDocs, Term term) {
211:
212: try {
213:
214: int count = 0;
215:
216: TermDocs docs = reader.termDocs(term);
217: if (docs != null) {
218: while (docs.next()) {
219: if (matchingDocs.get(docs.doc())) {
220: count++;
221: }
222: }
223: }
224:
225: return count;
226:
227: } catch (Exception e) {
228: e.printStackTrace();
229: }
230:
231: return 0;
232: }
233:
234: /**
235: * Method executeRequest
236: *
237: * @throws WebdavException
238: *
239: * @version 12/28/2001
240: */
241: protected void executeRequest() throws WebdavException {
242:
243: resp.setContentType(TEXT_XML_UTF_8);
244:
245: org.jdom.Element rootElement = new org.jdom.Element("facets",
246: HIPPO_NS);
247:
248: org.jdom.Document responseDoc = new org.jdom.Document(
249: rootElement);
250:
251: if (((IBasicQuery) searchQuery).getExpression() instanceof AbstractLuceneExpression) {
252: AbstractLuceneExpression exp = (AbstractLuceneExpression) ((IBasicQuery) searchQuery)
253: .getExpression();
254:
255: IndexReader reader = null;
256: try {
257: reader = exp.getIndex().getReader();
258:
259: Query query = exp.getExecutableQuery();
260:
261: if (!requestedProperties.isAllProp()) {
262:
263: QueryFilter filter = new QueryFilter(query);
264: BitSet matchDocs = filter.bits(reader);
265:
266: Iterator props = requestedProperties
267: .getRequestedProperties();
268: while (props.hasNext()) {
269: RequestedProperty prop = (RequestedProperty) props
270: .next();
271:
272: Element property = new Element(prop.getName(),
273: Namespace.getNamespace(prop
274: .getNamespace()));
275:
276: String fieldname = IndexConfiguration
277: .generateFieldName(prop.getNamespace(),
278: prop.getName());
279:
280: boolean isInt = exp.getIndex()
281: .getConfiguration().isIntProperty(
282: prop.getNamespace(),
283: prop.getName());
284:
285: // only investigate if there are POTENTIAL matches
286: if (!matchDocs.isEmpty()) {
287: TermEnum terms = null;
288: try {
289: terms = reader.terms(new Term(
290: fieldname, ""));
291:
292: while (fieldname.equals(terms.term()
293: .field())) {
294: int hits = hitsForTermInQuery(
295: reader, matchDocs,
296: new Term(fieldname, terms
297: .term().text()));
298:
299: if (hits > 0) {
300: Element value = new Element(
301: "value", HIPPO_NS);
302: value.setAttribute("hits",
303: String.valueOf(hits));
304:
305: // some extra conversion here!
306: if (!isInt)
307: value.setText(terms.term()
308: .text());
309: else {
310: Number num = Index.INT_INDEX_FORMAT
311: .parse(terms.term()
312: .text());
313: value.setText(num
314: .toString());
315: }
316:
317: property.addContent(value);
318: }
319:
320: if (!terms.next())
321: break;
322: }
323: } finally {
324: if (terms != null) {
325: terms.close();
326: }
327: }
328: }
329:
330: rootElement.addContent(property);
331: }
332:
333: } else {
334: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
335: resp.setContentType(TEXT_XML_UTF_8);
336: createErrorResult(
337: SearchQueryResult.STATUS_BAD_QUERY,
338: "Can't enumerate 'all-props'! Use explicit list of requested properties.");
339: throw new WebdavException(
340: WebdavStatus.SC_BAD_REQUEST);
341: }
342:
343: } catch (IOException e) {
344: System.err.println(e.getMessage());
345: e.printStackTrace();
346: resp.setStatus(getErrorCode(e)); // no special handling needed
347: throw new WebdavException(WebdavStatus.SC_ACCEPTED,
348: false); // abort the TA
349: } catch (ParseException e) {
350: System.err.println(e.getMessage());
351: e.printStackTrace();
352: resp.setStatus(getErrorCode(e)); // no special handling needed
353: throw new WebdavException(WebdavStatus.SC_ACCEPTED,
354: false); // abort the TA
355: } catch (SearchException e) {
356: System.err.println(e.getMessage());
357: e.printStackTrace();
358: resp.setStatus(getErrorCode(e)); // no special handling needed
359: throw new WebdavException(WebdavStatus.SC_ACCEPTED,
360: false); // abort the TA
361: } finally {
362: if (reader != null) {
363: try {
364: exp.getIndex().releaseReader(reader);
365: } catch (Exception e) {
366: e.printStackTrace();
367: }
368: }
369: }
370: }
371:
372: resp.setStatus(WebdavStatus.SC_OK);
373:
374: try {
375: sendResult(responseDoc);
376: } catch (Exception e) {
377: System.err.println(e.getMessage());
378: e.printStackTrace();
379: resp.setStatus(getErrorCode(e)); // no special handling needed
380: throw new WebdavException(WebdavStatus.SC_ACCEPTED, false); // abort the TA
381: }
382:
383: }
384:
385: /**
386: * Method getSearchRequestElement
387: *
388: * @return an Element
389: *
390: * @throws WebdavException
391: *
392: * @version 12/28/2001
393: */
394: private Element getQueryElement() throws WebdavException,
395: JDOMException {
396: Element queryElement = null;
397: try {
398: Document document = parseRequestContent(); // TODO: check root element name
399: Element rootElement = document.getRootElement();
400: List children = rootElement.getChildren();
401: if (children.size() > 0) {
402: queryElement = (Element) children.get(0);
403: }
404: return queryElement;
405:
406: } catch (IOException e) {
407: System.err.println(e.getMessage());
408: e.printStackTrace();
409: resp.setStatus(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
410: throw new WebdavException(
411: WebdavStatus.SC_INTERNAL_SERVER_ERROR);
412: }
413: }
414:
415: /**
416: * Method sendResult
417: *
418: * @param responseDoc a Document
419: *
420: * @throws JDOMException
421: * @throws IOException
422: *
423: */
424: private void sendResult(org.jdom.Document responseDoc)
425: throws org.jdom.JDOMException, IOException {
426: org.jdom.output.Format format = org.jdom.output.Format
427: .getPrettyFormat();
428: format.setIndent(XML_RESPONSE_INDENT);
429: XMLOutputter xmlWriter = new XMLOutputter(format);
430: xmlWriter.output(responseDoc, resp.getWriter());
431: }
432:
433: /**
434: * Method createErrorResult
435: *
436: * @param queryStatus an int
437: * @param message a String
438: *
439: */
440: private void createErrorResult(int queryStatus, String message)
441: throws WebdavException {
442: SearchQueryResult result = new SearchQueryResult();
443: result.setStatus(queryStatus);
444: result.setDescription(message);
445: result.setHref(getSlideContextPath());
446:
447: try {
448: WebdavResult webdavResult = new WebdavResult(result, null);
449: org.jdom.Document responseDoc = webdavResult
450: .getWebdavResultDocument();
451: sendResult(responseDoc);
452: } catch (Exception e) {
453: System.err.println(e.getMessage());
454: e.printStackTrace();
455: resp.setStatus(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
456: throw new WebdavException(
457: WebdavStatus.SC_INTERNAL_SERVER_ERROR);
458: }
459: }
460:
461: /**
462: * Represents the webdav result for a SEARCH. It encapsulates the response
463: * body, and the status of the response. It contains a list of response
464: * elements with propstat elements and optional a response element with
465: * an error status and a responsedescription
466: */
467: class WebdavResult {
468:
469: private SearchQueryResult queryResult;
470: private org.jdom.Document responseDoc;
471:
472: /** the status of the response */
473: private int webdavStatus = WebdavStatus.SC_MULTI_STATUS;
474:
475: /**
476: * constructs a WebdavResult
477: *
478: * @param queryResult the result of the query
479: * @param retriever the retriever to get the properties
480: *
481: * @throws JDOMException
482: * @throws SlideException
483: *
484: */
485: WebdavResult(SearchQueryResult queryResult,
486: PropertyRetriever retriever)
487: throws org.jdom.JDOMException, SlideException {
488: this .queryResult = queryResult;
489: init();
490: }
491:
492: /**
493: * Method getWebdavStatus
494: *
495: * @return an int
496: *
497: */
498: int getWebdavStatus() {
499: return webdavStatus;
500: }
501:
502: /**
503: * Method getWebdabResultDocument
504: *
505: * @return a Document
506: *
507: */
508: org.jdom.Document getWebdavResultDocument() {
509: return responseDoc;
510: }
511:
512: /**
513: * Method init
514: *
515: * @throws JDOMException
516: * @throws SlideException
517: *
518: */
519: private void init() throws org.jdom.JDOMException,
520: SlideException {
521:
522: String href = null;
523: int errorStatus = webdavStatus;
524:
525: org.jdom.Element rootElement = new org.jdom.Element(
526: E_MULTISTATUS, DNSP);
527:
528: responseDoc = new org.jdom.Document(rootElement);
529:
530: Iterator it = queryResult.iterator();
531: while (it.hasNext()) {
532:
533: org.jdom.Element responseElement = new org.jdom.Element(
534: E_RESPONSE, DNSP);
535:
536: rootElement.addContent(responseElement);
537: org.jdom.Element hrefElement = new org.jdom.Element(
538: E_HREF, DNSP);
539:
540: RequestedResource resource = (RequestedResource) it
541: .next();
542: String internalUri = resource.getUri();
543:
544: String absUri = WebdavUtils.getAbsolutePath(
545: internalUri, req, getConfig());
546:
547: hrefElement.addContent(absUri);
548:
549: responseElement.addContent(hrefElement);
550:
551: }
552:
553: int status = queryResult.getStatus();
554: if (status != SearchQueryResult.STATUS_OK) {
555:
556: //String webdavStatusText = null;
557:
558: switch (status) {
559: case SearchQueryResult.STATUS_BAD_QUERY:
560: webdavStatus = WebdavStatus.SC_BAD_REQUEST;
561: errorStatus = WebdavStatus.SC_BAD_REQUEST;
562: href = queryResult.getHref();
563: break;
564:
565: case SearchQueryResult.STATUS_INVALID_SCOPE:
566: webdavStatus = WebdavStatus.SC_BAD_REQUEST;
567: errorStatus = WebdavStatus.SC_NOT_FOUND;
568: href = queryResult.getHref();
569: break;
570:
571: case SearchQueryResult.STATUS_PARTIAL_RESULT:
572: errorStatus = WebdavStatus.SC_INSUFFICIENT_STORAGE;
573: href = getSlideContextPath();
574: break;
575:
576: case SearchQueryResult.STATUS_UNPROCESSABLE_ENTITY:
577: errorStatus = WebdavStatus.SC_UNPROCESSABLE_ENTITY;
578: href = getSlideContextPath();
579: break;
580:
581: case SearchQueryResult.STATUS_BAD_GATEWAY:
582: errorStatus = WebdavStatus.SC_BAD_GATEWAY;
583: href = queryResult.getHref();
584: break;
585:
586: case SearchQueryResult.STATUS_FORBIDDEN:
587: errorStatus = WebdavStatus.SC_FORBIDDEN;
588: href = queryResult.getHref();
589: break;
590:
591: default:
592: throw new WebdavException(
593: WebdavStatus.SC_INTERNAL_SERVER_ERROR);
594:
595: }
596: org.jdom.Element responseElement = new org.jdom.Element(
597: E_RESPONSE, DNSP);
598:
599: org.jdom.Element hrefElement = new org.jdom.Element(
600: E_HREF, DNSP);
601:
602: hrefElement.addContent(href);
603:
604: org.jdom.Element statusElement = new org.jdom.Element(
605: E_STATUS, DNSP);
606:
607: statusElement.addContent(getStatusText(errorStatus));
608: responseElement.addContent(hrefElement);
609: responseElement.addContent(statusElement);
610:
611: String description = queryResult.getDescription();
612: if (description != null) {
613: org.jdom.Element responseDescriptionElement = new org.jdom.Element(
614: E_RESPONSEDESCRIPTION, DNSP);
615:
616: responseDescriptionElement.addContent(description);
617: responseElement
618: .addContent(responseDescriptionElement);
619: }
620:
621: if (status == SearchQueryResult.STATUS_INVALID_SCOPE) {
622: responseElement.addContent(new org.jdom.Element(
623: "scopeerror", DNSP));
624: }
625: rootElement.addContent(responseElement);
626: }
627: }
628:
629: /**
630: * Method getStatusText
631: *
632: * @param status an int
633: *
634: * @return a String
635: *
636: */
637: private String getStatusText(int status) {
638: return "HTTP/1.1 " + status + " "
639: + WebdavStatus.getStatusText(status);
640: }
641: }
642: }
|