/*
* 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.
*
* $Id: Propfind.java 551440 2007-06-28 04:26:07Z vgritsenko $
*/
package org.apache.xindice.webadmin.webdav.components;
import java.io.IOException;
import java.util.Set;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.Database;
import org.apache.xindice.core.data.Entry;
import org.apache.xindice.webadmin.webdav.components.props.CreationDate;
import org.apache.xindice.webadmin.webdav.components.props.DAVProperty;
import org.apache.xindice.webadmin.webdav.components.props.LastModified;
import org.apache.xindice.webadmin.webdav.components.props.DisplayName;
import org.apache.xindice.webadmin.webdav.components.props.ResourceType;
import org.apache.xindice.webadmin.webdav.components.props.ContentType;
import org.apache.xindice.webadmin.webdav.WebdavStatus;
import org.apache.xindice.webadmin.webdav.DAVRequest;
import org.apache.xindice.webadmin.webdav.DAVResponse;
import org.apache.xindice.webadmin.PartialResponse;
import org.apache.xindice.webadmin.Location;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class implements the Propfind command for WebDAV operations on
* Xindice. <br>
* <br>
* PROPFIND command requests properties defined for resources or collections.
* Request can have a body that contians XML document. It is possible
* to request particular property values, all property values, or a
* list of the names of the resource's properties. A client may choose not
* to submit a request body. An empty PROPFIND request body is treated as a
* request for the names and values of all properties.<br>
* <br>
* A client may submit a Depth header with a value of "0", "1", or "infinity"
* with a PROPFIND on a collection. By default, the PROPFIND method without a
* Depth header acts as if a "Depth: infinity" header was included.<br>
* <br>
* The default operation status code is 207 (Multi-Status).
*
* @author <a href="mailto:jmetzner@apache.org">Jan Metzner</a>
* @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
* @version $Revision: 551440 $, $Date: 2007-06-28 00:26:07 -0400 (Thu, 28 Jun 2007) $
*/
public class Propfind implements DAVComponent {
public static final int FIND_BY_PROPERTY = 1;
public static final int FIND_PROPERTY_NAMES = 2;
public static final int FIND_ALL_PROP = 3;
private static final HashMap PROPS_MAP = new HashMap();
static {
DAVProperty[] props = {
new CreationDate(),
new LastModified(),
new DisplayName(),
new ResourceType(),
new ContentType()
};
for (int i = 0; i < props.length; i++) {
PROPS_MAP.put(props[i].getName(), props[i]);
}
}
private static final Log log = LogFactory.getLog(Propfind.class);
public void execute(DAVRequest req, DAVResponse res, Location target) throws ServletException, IOException {
if (!target.isRoot() && target.getCollection() == null) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
try {
process(req, res, target.getCollection(), target.getName());
} catch (DBException e) {
log.error(e);
throw new ServletException(e);
}
}
private void process(DAVRequest req, DAVResponse res, Collection col, String name)
throws DBException, IOException {
res.setProtocol(req.getProtocol());
Document requestDoc = req.getRequestDoc();
int type = getPropFindType(requestDoc);
Set requestedProps = null;
if (type == FIND_BY_PROPERTY) {
requestedProps = getRequestPropertyNames(requestDoc);
}
String contextPath = req.getContextPath();
if (name == null) {
int depth = req.getDepth();
String requestPath = req.getPath();
if (requestPath == null || requestPath.equals("/")) { // root
col = null;
}
HashMap values = getProperties(col, null, requestedProps, type);
addPropfindResponse(res, getLocation(contextPath, col), values);
if (depth != 0) {
processChildren(res, contextPath, col, requestedProps, type, depth);
}
} else {
Entry entry = col.getEntry(name);
if (entry == null) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
} else {
HashMap values = getProperties(col, entry, requestedProps, type);
addPropfindResponse(res, getLocation(contextPath, col, name), values);
}
}
res.closeMultistatusResponse();
}
private void processChildren(DAVResponse res, String contextPath, Collection col, Set requestedProps, int type, int depth)
throws DBException, IOException {
String[] children;
if (col == null) {
children = Database.listDatabases();
} else {
children = col.listCollections();
}
if (depth != 0) {
// child resources
if (col != null) {
String[] childResources = col.listDocuments();
for (int i = 0; i < childResources.length; i++) {
HashMap values = getProperties(col, col.getEntry(childResources[i]), requestedProps, type);
addPropfindResponse(res, getLocation(contextPath, col, childResources[i]), values);
}
}
// child collections
for (int i = 0; i < children.length; i++) {
Collection childCol = col != null ?
col.getCollection(children[i]) :
Database.getDatabase(children[i]);
HashMap values = getProperties(childCol, null, requestedProps, type);
addPropfindResponse(res, getLocation(contextPath, childCol), values);
processChildren(res, contextPath, childCol, requestedProps, type, depth - 1 );
}
}
}
private HashMap getProperties(Collection col, Entry entry, Set requestedProps, int type) {
HashMap values = null;
switch (type) {
case FIND_PROPERTY_NAMES:
values = getPropertyNames(col, entry);
break;
case FIND_ALL_PROP:
values = getAllPropertiesValues(col, entry);
break;
case FIND_BY_PROPERTY:
values = getPropertiesValues(col, entry, requestedProps);
break;
}
return values;
}
private HashMap getPropertyNames(Collection col, Entry entry) {
ArrayList values = new ArrayList();
for (Iterator i = PROPS_MAP.keySet().iterator(); i.hasNext(); ) {
String property = (String) i.next();
if (getProperty(col, entry, property) != null) {
values.add(new PartialResponse.Content(property));
}
}
HashMap status = new HashMap();
status.put(new Integer(WebdavStatus.SC_OK), values);
return status;
}
private HashMap getPropertiesValues(Collection col, Entry entry, Set requestProps) {
ArrayList values = new ArrayList();
ArrayList notFound = new ArrayList();
for (Iterator i = requestProps.iterator(); i.hasNext(); ) {
String requestProp = (String) i.next();
if (PROPS_MAP.containsKey(requestProp)) { // supported property
PartialResponse.Content part = getProperty(col, entry, requestProp);
if (part != null) {
values.add(part);
} else {
notFound.add(new PartialResponse.Content(requestProp));
}
} else { // requested property not supported
notFound.add(new PartialResponse.Content(requestProp));
}
}
HashMap status = new HashMap();
status.put(new Integer(WebdavStatus.SC_OK), values);
status.put(new Integer(WebdavStatus.SC_NOT_FOUND), notFound);
return status;
}
private HashMap getAllPropertiesValues(Collection col, Entry entry) {
ArrayList values = new ArrayList();
Set props = PROPS_MAP.keySet();
for (Iterator i = props.iterator(); i.hasNext(); ) {
String requestProp = (String) i.next();
PartialResponse.Content part = getProperty(col, entry, requestProp);
if (part != null) {
values.add(part);
}
}
HashMap status = new HashMap();
status.put(new Integer(WebdavStatus.SC_OK), values);
return status;
}
private PartialResponse.Content getProperty(Collection col, Entry entry, String propName) {
DAVProperty prop = (DAVProperty) PROPS_MAP.get(propName);
PartialResponse.Content part;
if (entry != null) {
part = prop.getProperty(entry);
} else if (col != null) {
part = prop.getProperty(col);
} else {
part = prop.getRootProperty();
}
return part;
}
private Set getRequestPropertyNames(Document doc) {
NodeList nodes = doc.getDocumentElement().getElementsByTagNameNS("DAV:", "prop");
HashSet properties = new HashSet();
NodeList childList = nodes.item(0).getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE && currentNode.getNamespaceURI().equals("DAV:")) {
properties.add(currentNode.getLocalName());
}
}
if (log.isDebugEnabled()) {
log.debug("Requested properties: " + properties.toString());
}
return properties;
}
private int getPropFindType(Document doc) {
if (doc == null) {
return FIND_ALL_PROP;
}
int type = FIND_ALL_PROP;
Node propfind = doc.getDocumentElement();
if (propfind != null && isNodeNamed(propfind, "propfind")) {
NodeList childList = propfind.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
if (isNodeNamed(currentNode, "prop")) {
type = FIND_BY_PROPERTY;
break;
} else if (isNodeNamed(currentNode, "propname")) {
type = FIND_PROPERTY_NAMES;
break;
} else if (isNodeNamed(currentNode, "allprop")) {
type = FIND_ALL_PROP;
break;
}
}
}
}
return type;
}
private boolean isNodeNamed(Node node, String name) {
return node.getNamespaceURI().equals("DAV:") && node.getLocalName().equals(name);
}
private String getLocation(String context, Collection col) {
return context + (col != null ? col.getCanonicalName() : "") + "/";
}
private String getLocation(String context, Collection col, String resource) {
return context + col.getCanonicalDocumentName(resource);
}
public void addPropfindResponse(DAVResponse res, String location, HashMap statusList) throws IOException {
PartialResponse response = new PartialResponse(location);
for (Iterator i = statusList.keySet().iterator(); i.hasNext(); ) {
Integer status = (Integer) i.next();
ArrayList props = (ArrayList) statusList.get(status);
if (props != null && props.size() > 0) {
PartialResponse.Content prop = new PartialResponse.Content("prop");
for (Iterator j = props.iterator(); j.hasNext(); ) {
prop.addChild((PartialResponse.Content) j.next());
}
PartialResponse.Content stat = new PartialResponse.Content("status");
stat.setValue(res.getProtocol() + " " + status + " " + WebdavStatus.getStatusText(status.intValue()));
PartialResponse.Content propstat = new PartialResponse.Content("propstat");
propstat.addChild(prop);
propstat.addChild(stat);
response.addContent(propstat);
}
}
res.sendMultiStatusResponse(response);
}
}