/*
* Copyright 2012 McEvoy Software Ltd.
*/
package io.milton.http.caldav;
import io.milton.http.ResourceFactory;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.report.Report;
import io.milton.http.values.HrefList;
import io.milton.http.values.PropFindResponseList;
import io.milton.http.values.ValueAndType;
import io.milton.http.webdav.PropFindPropertyBuilder;
import io.milton.http.webdav.PropFindResponse;
import io.milton.http.webdav.PropFindXmlGenerator;
import io.milton.http.webdav.PropertiesRequest;
import io.milton.http.webdav.PropertiesRequest.Property;
import io.milton.http.webdav.WebDavProtocol;
import io.milton.resource.PropFindableResource;
import io.milton.resource.Resource;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.namespace.QName;
import org.jdom.Document;
import org.jdom.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* http://tools.ietf.org/html/rfc3253#section-3.8
*
* Many property values are defined as a DAV:href, or a set of DAV:href
elements. The DAV:expand-property report provides a mechanism for
retrieving in one request the properties from the resources
identified by those DAV:href elements. This report not only
decreases the number of requests required, but also allows the server
to minimize the number of separate read transactions required on the
underlying versioning store.
The DAV:expand-property report SHOULD be supported by all resources
that support the REPORT method.
Marshalling:
The request body MUST be a DAV:expand-property XML element.
<!ELEMENT expand-property (property*)>
<!ELEMENT property (property*)>
<!ATTLIST property name NMTOKEN #REQUIRED>
name value: a property element type
<!ATTLIST property namespace NMTOKEN "DAV:">
namespace value: an XML namespace
The response body for a successful request MUST be a
DAV:multistatus XML element.
multistatus: see RFC 2518, Section 12.9
The properties reported in the DAV:prop elements of the
DAV:multistatus element MUST be those identified by the
DAV:property elements in the DAV:expand-property element. If
there are DAV:property elements nested within a DAV:property
element, then every DAV:href in the value of the corresponding
property is replaced by a DAV:response element whose DAV:prop
elements report the values of the properties identified by the
nested DAV:property elements. The nested DAV:property elements
can in turn contain DAV:property elements, so that multiple levels
of DAV:href expansion can be requested.
Note that a validating parser MUST be aware that the DAV:expand-
property report effectively modifies the DTD of every property by
replacing every occurrence of "href" in the DTD with "href |
response".
*
* REPORT /foo.html HTTP/1.1
Host: www.webdav.org
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:expand-property xmlns:D="DAV:">
<D:property name="version-history">
<D:property name="version-set">
<D:property name="creator-displayname"/>
<D:property name="activity-set"/>
</D:property>
</D:property>
</D:expand-property>
>>RESPONSE
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>http://www.webdav.org/foo.html</D:href>
<D:propstat>
<D:prop>
<D:version-history>
<D:response>
<D:href>http://repo.webdav.org/his/23</D:href>
<D:propstat>
<D:prop>
<D:version-set>
<D:response>
<D:href>http://repo.webdav.org/his/23/ver/1</D:href>
<D:propstat>
<D:prop>
<D:creator-displayname>Fred</D:creator-displayname>
<D:activity-set>
<D:href>http://www.webdav.org/ws/dev/sally</D:href>
</D:activity-set> </D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat> </D:response>
<D:response>
<D:href>http://repo.webdav.org/his/23/ver/2</D:href>
<D:propstat>
<D:prop>
<D:creator-displayname>Sally</D:creator-displayname>
<D:activity-set>
<D:href>http://repo.webdav.org/act/add-refresh-cmd</D:href>
</D:activity-set> </D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat> </D:response>
</D:version-set> </D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat> </D:response>
</D:version-history> </D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat> </D:response>
</D:multistatus>
In this example, the DAV:creator-displayname and DAV:activity-set
properties of the versions in the DAV:version-set of the
DAV:version-history of http://www.webdav.org/foo.html are reported.
*
* @author bradm
*/
public class ExpandPropertyReport implements Report {
private static final Logger log = LoggerFactory.getLogger(MultiGetReport.class);
private final ResourceFactory resourceFactory;
private final PropFindPropertyBuilder propertyBuilder;
private final PropFindXmlGenerator xmlGenerator;
public ExpandPropertyReport(ResourceFactory resourceFactory, PropFindPropertyBuilder propertyBuilder, PropFindXmlGenerator xmlGenerator) {
this.resourceFactory = resourceFactory;
this.propertyBuilder = propertyBuilder;
this.xmlGenerator = xmlGenerator;
}
@Override
public String process(String host, String path, Resource calendar, Document doc) throws NotAuthorizedException, BadRequestException {
log.debug("process");
PropertiesRequest parseResult = parse(doc.getRootElement());
List<PropFindResponse> propFindResponses;
try {
PropFindableResource pfr = (PropFindableResource) calendar;
propFindResponses = propertyBuilder.buildProperties(pfr, 1, parseResult, path);
for (PropFindResponse r : propFindResponses) {
Set<Entry<QName, ValueAndType>> set = r.getKnownProperties().entrySet();
set = new HashSet<Entry<QName, ValueAndType>>(set);
for (Entry<QName, ValueAndType> p : set) {
Object val = p.getValue().getValue();
QName name = p.getKey();
if (val instanceof HrefList) {
HrefList hrefList = (HrefList) val;
Property prop = parseResult.get(name);
PropFindResponseList propFindResponseList = toResponseList(host, hrefList, prop);
replaceHrefs(host, propFindResponseList, prop);
r.getKnownProperties().remove(name);
r.getKnownProperties().put(name, new ValueAndType(propFindResponseList, PropFindResponseList.class));
}
}
}
} catch (URISyntaxException ex) {
throw new RuntimeException("Exception parsing url, indicating the requested URL is not correctly encoded. Please check the client application.", ex);
}
//show("",propFindResponses);
String xml = xmlGenerator.generate(propFindResponses);
return xml;
}
public PropertiesRequest parse(Element elProp) {
Set<Property> set = new HashSet<Property>();
for (Object o : elProp.getChildren()) {
if (o instanceof Element) {
Element el = (Element) o;
if (el.getName().equals("property")) {
QName name = getQName(el);
Set<Property> nested = parseChildren(el);
Property p = new Property(name, nested);
set.add(p);
}
}
}
PropertiesRequest pr = new PropertiesRequest(set);
return pr;
}
private Set<Property> parseChildren(Element elProp) {
Set<Property> set = new HashSet<Property>();
for (Object o : elProp.getChildren()) {
if (o instanceof Element) {
Element el = (Element) o;
if (el.getName().equals("property")) {
QName name = getQName(el);
Set<Property> nested = parseChildren(el);
Property p = new Property(name, nested);
set.add(p);
}
}
}
return set;
}
private QName getQName(Element el) {
String local = el.getAttributeValue("name");
String ns = el.getAttributeValue("namespace");
if (ns == null) {
ns = WebDavProtocol.DAV_URI;
}
QName name = new QName(ns, local);
return name;
}
@Override
public String getName() {
return "expand-property";
}
private PropFindResponseList toResponseList(String host, HrefList hrefList, Property prop) throws URISyntaxException, NotAuthorizedException, BadRequestException {
PropFindResponseList list = new PropFindResponseList();
for (String href : hrefList) {
Resource r = resourceFactory.getResource(host, href);
if (r != null) {
if (r instanceof PropFindableResource) {
PropFindableResource pfr = (PropFindableResource) r;
PropertiesRequest propertyRequest = new PropertiesRequest(prop.getNested());
List<PropFindResponse> propFindResponses = propertyBuilder.buildProperties(pfr, 0, propertyRequest, href);
// should be only one
list.addAll(propFindResponses);
}
}
}
return list;
}
private void replaceHrefs(String host, PropFindResponseList propFindResponseList, Property prop) throws URISyntaxException, NotAuthorizedException, BadRequestException {
for (PropFindResponse r : propFindResponseList) {
Set<Entry<QName, ValueAndType>> set = r.getKnownProperties().entrySet();
set = new HashSet<Entry<QName, ValueAndType>>(set);
for (Entry<QName, ValueAndType> p : set) {
Object val = p.getValue().getValue();
QName name = p.getKey();
if (val instanceof HrefList) {
HrefList hrefList = (HrefList) val;
Property nestedProp = prop.getNestedMap().get(name);
// Check for another level of nesting
if (nestedProp != null && nestedProp.getNested() != null && !nestedProp.getNested().isEmpty())
{
PropFindResponseList nestedList = toResponseList(host, hrefList, nestedProp);
replaceHrefs(host, nestedList, nestedProp);
r.getKnownProperties().remove(name);
r.getKnownProperties().put(name, new ValueAndType(nestedList, PropFindResponseList.class));
}
}
}
}
}
private void show(String prefix, List<PropFindResponse> propFindResponses) {
for( PropFindResponse p : propFindResponses ) {
for( Entry<QName, ValueAndType> e : p.getKnownProperties().entrySet()) {
Object o = e.getValue().getValue();
if( o instanceof PropFindResponseList) {
PropFindResponseList childList = (PropFindResponseList) o;
show(prefix + " ", childList);
} else {
}
}
}
}
}