/*
* $Header: /home/cvs/jakarta-slide/src/webdav/server/org/apache/slide/webdav/method/PropFindMethod.java,v 1.105.2.1 2004/09/17 15:39:34 luetzkendorf Exp $
* $Revision: 1.105.2.1 $
* $Date: 2004/09/17 15:39:34 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed 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.slide.webdav.method;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.PropertyParseException;
import org.apache.slide.common.RequestedProperties;
import org.apache.slide.common.RequestedPropertiesImpl;
import org.apache.slide.common.RequestedProperty;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.SlideTokenWrapper;
import org.apache.slide.common.UriPath;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.event.EventDispatcher;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.StructureException;
import org.apache.slide.util.Configuration;
import org.apache.slide.webdav.WebdavException;
import org.apache.slide.webdav.WebdavServletConfig;
import org.apache.slide.webdav.event.WebdavEvent;
import org.apache.slide.webdav.util.AclConstants;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.LabeledRevisionNotFoundException;
import org.apache.slide.webdav.util.PropertyRetrieverImpl;
import org.apache.slide.webdav.util.UnlockListenerImpl;
import org.apache.slide.webdav.util.VersioningHelper;
import org.apache.slide.webdav.util.WebdavStatus;
import org.apache.slide.webdav.util.WebdavUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
/**
* PROPFIND method.
*
*/
public class PropFindMethod extends AbstractWebdavMethod implements DeltavConstants, AclConstants, ReadMethod {
/**
* Specify a property mask.
*/
protected static final int FIND_BY_PROPERTY = 0;
/**
* Display all properties.
*/
protected static final int FIND_ALL_PROP = 1;
/**
* Return property names.
*/
protected static final int FIND_PROPERTY_NAMES = 2;
// ----------------------------------------------------- Instance Variables
/**
* Depth.
*/
protected int depth;
/**
* Type of the PROPFIND method.
*/
protected int propFindType;
/** if true, an ALL_PROP request will include computed props */
protected boolean extendedAllprop = false;
/**
** The SAXBuilder used to create JDOM Documents.
**/
protected static SAXBuilder saxBuilder = null;
/**
* The list of requested properties.
*/
protected RequestedProperties requestedProperties = null;
/**
* Resource to be retrieved.
*/
protected String resourcePath;
/**
* The VersioningHelper used by this instance.
*/
protected VersioningHelper versioningHelper = null;
/**
* The value of the <code>Label</code> header.
*/
protected String labelHeader = null;
/**
* If set <code>true</code>, instead of creating the complete response document
* in memory and then sending it to the client, available parts of the response
* are send immediatly in order to reduce the memory footprint.
*/
protected boolean outputOptimized = true;
// ----------------------------------------------------------- Constructors
/**
* Constructor.
*
* @param token the token for accessing the namespace
* @param config configuration of the WebDAV servlet
*/
public PropFindMethod(NamespaceAccessToken token,
WebdavServletConfig config) {
super(token, config);
}
// ------------------------------------------------------ Protected Methods
/**
* Parse the request.
*
* @exception WebdavException Bad request
*/
protected void parseRequest() throws WebdavException {
versioningHelper = VersioningHelper.getVersioningHelper(
slideToken, token, req, resp, getConfig() );
// readRequestContent();
depth = INFINITY;
propFindType = FIND_ALL_PROP;
extendedAllprop = getBooleanInitParameter( "extendedAllprop" );
outputOptimized = getBooleanInitParameter( "optimizePropfindOutput" );
resourcePath = requestUri;
if (resourcePath == null) {
resourcePath = "/";
}
labelHeader = WebdavUtils.fixTomcatHeader(requestHeaders.getLabel(), "UTF-8");
retrieveDepth();
if (req.getContentLength() == 0) {
requestedProperties = new RequestedPropertiesImpl();
requestedProperties.setIsAllProp(true);
propFindType = FIND_ALL_PROP;
}
else {
try {
Element element = parseRequestContent(E_PROPFIND);
try {
element = (Element)element.getChildren().get(0);
}
catch (Exception e) {
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, getClass().getName()+".missingRootElementChildren", new Object[]{"DAV:"+E_PROPFIND} );
throw new WebdavException( statusCode );
}
if (element.getName().equalsIgnoreCase(E_PROPNAME)){
propFindType = FIND_PROPERTY_NAMES;
}
else if ( element.getName().equalsIgnoreCase(E_PROP) ) {
requestedProperties = new RequestedPropertiesImpl(element);
propFindType = FIND_BY_PROPERTY;
}
else if ( element.getName().equalsIgnoreCase(E_ALLPROP) ) {
requestedProperties = new RequestedPropertiesImpl(element);
propFindType = FIND_ALL_PROP;
}
else {
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, getClass().getName()+".invalidChildOfRootElement", new Object[]{element.getNamespace()+":"+element.getName(),"DAV:"+E_PROPFIND} );
throw new WebdavException( statusCode );
}
}
catch (JDOMException e){
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch (PropertyParseException e){
int statusCode = WebdavStatus.SC_BAD_REQUEST;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch (IOException e){
int statusCode = WebdavStatus.SC_INTERNAL_SERVER_ERROR;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
}
}
/**
* Retrieves the <code>Depth</code> header from the request.
*/
private void retrieveDepth() throws WebdavException {
depth = requestHeaders.getDepth(INFINITY);
// limit tree browsing a bit
if (depth > getConfig().getDepthLimit()) {
depth = getConfig().getDepthLimit();
}
}
/**
* Execute the request.
*
* @exception WebdavException
*/
protected void executeRequest() throws IOException, WebdavException {
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
// Loads the associated object from the store.
// Get the object from Data.
ObjectNode resource = null;
try {
if ( WebdavEvent.PROPFIND.isEnabled() ) EventDispatcher.getInstance().fireVetoableEvent(WebdavEvent.PROPFIND, new WebdavEvent(this));
resource = structure.retrieve(slideToken, resourcePath);
} catch (StructureException e) {
int statusCode = WebdavStatus.SC_NOT_FOUND;
sendError( statusCode, e );
throw new WebdavException( statusCode );
} catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
resp.setContentType(TEXT_XML_UTF_8);
// Create multistatus object
Element multistatusElement = new Element(E_MULTISTATUS, DNSP);
org.jdom.output.Format format = org.jdom.output.Format.getPrettyFormat();
format.setIndent(XML_RESPONSE_INDENT);
XMLOutputter xmlOutputter = new XMLOutputter(format);
if (resource != null) {
if (depth == 0) {
multistatusElement.addContent(getPropertiesOfObject(resource.getUri()));
xmlOutputter.output(new Document(multistatusElement), resp.getWriter());
} else {
// The stack always contains the object of the current level
Stack stack = new Stack();
stack.push(resource);
// Stack of the objects one level below
Stack stackBelow = new Stack();
StringBuffer buffer = new StringBuffer();
if (outputOptimized) {
resp.getWriter().write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
resp.getWriter().write("\n");
String namespacePrefix = multistatusElement.getNamespacePrefix();
if ( (namespacePrefix != null) && (namespacePrefix.length() == 0) ) {
namespacePrefix = null;
}
String namespaceUri = multistatusElement.getNamespaceURI();
if ( (namespaceUri != null) && (namespaceUri.length() == 0) ) {
namespaceUri = null;
}
buffer.append("<");
buffer.append(multistatusElement.getQualifiedName());
if (namespaceUri != null) {
buffer.append(" xmlns");
if (namespacePrefix != null) {
buffer.append(":");
buffer.append(namespacePrefix);
}
buffer.append("=\"");
buffer.append(namespaceUri);
buffer.append("\"");
}
buffer.append(">");
resp.getWriter().write(buffer.toString());
resp.getWriter().write("\n");
}
while ((!stack.isEmpty()) && (depth >= 0)) {
ObjectNode cur = (ObjectNode) stack.pop();
Element response = getPropertiesOfObject(cur.getUri());
if (outputOptimized) {
xmlOutputter.output(response, resp.getWriter());
}
else {
multistatusElement.addContent(response);
}
if (depth > 0) {
Enumeration enum = null;
try {
enum = structure.getChildren(slideToken, cur);
} catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
while (enum.hasMoreElements()) {
// PROPFIND must not list Lock-Null resources that
// are timed out
ObjectNode node = (ObjectNode)enum.nextElement();
UnlockListenerImpl listener = new UnlockListenerImpl(
slideToken, token, config, req, resp);
try {
lock.clearExpiredLocks(slideToken, node.getUri(), listener);
if (!listener.isRemovedLockResource(node.getUri())) {
stackBelow.push(node);
}
} catch (SlideException e) { /* ignore */}
}
}
if (stack.isEmpty()) {
depth--;
stack = stackBelow;
stackBelow = new Stack();
}
}
if (outputOptimized) {
resp.getWriter().write("\n");
buffer.setLength(0);
buffer.append("</");
buffer.append(multistatusElement.getQualifiedName());
buffer.append(">");
resp.getWriter().write(buffer.toString());
resp.getWriter().write("\n");
}
else {
xmlOutputter.output(new Document(multistatusElement), resp.getWriter());
}
}
}
}
/**
* Return the properties of an object as a <code><response></code>.
*
* @param resourceUri the slide URI of the resource.
*
* @return the <code><response></code> Element.
*
* @exception WebdavException
*/
protected Element getPropertiesOfObject(String resourceUri)
throws WebdavException {
SlideToken lightSToken = new SlideTokenWrapper(slideToken);
lightSToken.setForceLock(false);
// evaluate "Label" header
if (Configuration.useVersionControl()) {
try {
resourceUri = versioningHelper.getLabeledResourceUri(resourceUri, labelHeader);
}
catch (LabeledRevisionNotFoundException e) {
return getErrorResponse(resourceUri,
WebdavStatus.SC_CONFLICT,
DeltavConstants.C_MUST_SELECT_VERSION_IN_HISTORY);
}
catch( RevisionDescriptorNotFoundException x ) {
}
catch (SlideException e) {
return getErrorResponse(resourceUri, getErrorCode(e), null);
}
}
ObjectNode object = null;
try {
object = structure.retrieve(lightSToken, resourceUri);
}
catch (SlideException e) {
return getErrorResponse(resourceUri, getErrorCode(e), null);
}
Element responseElement = new Element(E_RESPONSE, DNSP);
String status = new String(HTTP_VERSION + WebdavStatus.SC_OK + " "
+ WebdavStatus.getStatusText
(WebdavStatus.SC_OK));
NodeRevisionDescriptors revisionDescriptors = null;
NodeRevisionDescriptor revisionDescriptor = null;
//boolean isCollection = false;
// NodeLock objectLockToken = null;
try {
Element hrefElement = new Element(E_HREF, DNSP);
//VersioningHelper vHelp = VersioningHelper.getVersioningHelper(
// lightSToken, token, req, resp, getConfig() );
String resourcePath = object.getUri();
revisionDescriptors =
content.retrieve(lightSToken, resourcePath);
try {
revisionDescriptor = content.retrieve(lightSToken,
revisionDescriptors);
//isCollection = WebdavUtils.isCollection(revisionDescriptor);
hrefElement.setText(
WebdavUtils.getAbsolutePath(object.getUri(), req,
getConfig()));
} catch (RevisionDescriptorNotFoundException e) {
// The object doesn't have any revision, we create a dummy
// NodeRevisionDescriptor object
//isCollection = true;
revisionDescriptor = new NodeRevisionDescriptor(0);
if (!Configuration.useBinding(token.getUri(lightSToken, object.getUri()).getStore())) {
revisionDescriptor.setName(new UriPath(object.getUri()).lastSegment());
}
hrefElement.setText(
WebdavUtils.getAbsolutePath(object.getUri(), req,
getConfig()));
}
responseElement.addContent(hrefElement);
// Enumeration lockTokens = lock.enumerateLocks(lightSToken, object.getUri(), true);
// if (lockTokens.hasMoreElements()) {
// objectLockToken = (NodeLock) lockTokens.nextElement();
// }
} catch (AccessDeniedException e) {
if (revisionDescriptor == null) {
revisionDescriptor = new NodeRevisionDescriptor(0);
}
} catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
PropertyRetrieverImpl propertyRetriever = new PropertyRetrieverImpl(token, lightSToken, getConfig());
switch (propFindType) {
case FIND_ALL_PROP :
case FIND_BY_PROPERTY :
try {
List propstatList= propertyRetriever.getPropertiesOfObject(requestedProperties, revisionDescriptors, revisionDescriptor, getSlideContextPath(), extendedAllprop);
Iterator iterator = propstatList.iterator();
while (iterator.hasNext()) {
responseElement.addContent((Element)iterator.next());
}
} catch (ServiceAccessException e) {
int statusCode = WebdavStatus.SC_FORBIDDEN;
sendError( statusCode, e );
throw new WebdavException( statusCode );
} catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
break;
case FIND_PROPERTY_NAMES :
try {
status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
+ " " + WebdavStatus.getStatusText
(WebdavStatus.SC_OK));
Element propstatElement = new Element(E_PROPSTAT, DNSP);
Element propElement = new Element(E_PROP, DNSP);
RequestedProperties propnames =
propertyRetriever.getAllPropertyNames(object.getUri(), true);
Iterator iterator = propnames.getRequestedProperties();
while (iterator.hasNext()) {
RequestedProperty p = (RequestedProperty)iterator.next();
Namespace nsp = DNSP.getURI().equals(p.getNamespace())
? DNSP
: Namespace.getNamespace(p.getNamespace());
propElement.addContent(new Element(p.getName(), nsp));
}
Element statusElement = new Element(E_STATUS, DNSP);
statusElement.setText(status);
propstatElement.addContent(propElement);
propstatElement.addContent(statusElement);
responseElement.addContent(propstatElement);
}
catch (ServiceAccessException e) {
int statusCode = WebdavStatus.SC_FORBIDDEN;
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
catch (Exception e) {
int statusCode = getErrorCode( e );
sendError( statusCode, e );
throw new WebdavException( statusCode );
}
break;
}
return responseElement;
}
/**
* Returns the appropriate <code><response></code> due to the given
* <code>exception</code> to the <code>generatedXML</code>
*
* @param resourcePath the URI of the request to display in the
* <code><href></code> element.
* @param errorCode the HTTP error code.
* @param condition the condition that has been violated.
*/
private Element getErrorResponse(String resourcePath, int errorCode, String condition) {
Element response = new Element(E_RESPONSE, DNSP);
Element href = new Element(E_HREF, DNSP);
href.setText(HTTP_PROTOCOL +
req.getServerName()+ ":" +
req.getServerPort() +
getSlideContextPath() +
resourcePath);
response.addContent(href);
Element propStat = new Element(E_PROPSTAT, DNSP);
response.addContent(propStat);
Element status = new Element(E_STATUS, DNSP);
status.setText(HTTP_VERSION + " " + errorCode + " " + WebdavStatus.getStatusText(errorCode));
propStat.addContent(status);
if (condition != null) {
Element responseDescriptiont = new Element(E_RESPONSEDESCRIPTION, DNSP);
Element errorElement = new Element(E_ERROR, DNSP);
responseDescriptiont.addContent(errorElement);
Element conditionElement = new Element(condition, DNSP);
errorElement.addContent(conditionElement);
propStat.addContent(responseDescriptiont);
}
return response;
}
}