package com.bradmcevoy.http.webdav;
import com.bradmcevoy.http.CollectionResource;
import com.bradmcevoy.http.DateUtils;
import com.bradmcevoy.http.GetableResource;
import com.bradmcevoy.property.PropertySource;
import com.bradmcevoy.http.Handler;
import com.bradmcevoy.http.HandlerHelper;
import com.bradmcevoy.http.HttpExtension;
import com.bradmcevoy.http.LockToken;
import com.bradmcevoy.http.LockableResource;
import com.bradmcevoy.http.PropFindableResource;
import com.bradmcevoy.http.PutableResource;
import com.bradmcevoy.http.Resource;
import com.bradmcevoy.http.ResourceHandlerHelper;
import com.bradmcevoy.http.XmlWriter;
import com.bradmcevoy.http.http11.DefaultETagGenerator;
import com.bradmcevoy.http.http11.ETagGenerator;
import com.bradmcevoy.http.quota.DefaultQuotaDataAccessor;
import com.bradmcevoy.http.quota.QuotaDataAccessor;
import com.bradmcevoy.http.values.SupportedReportSetList;
import com.bradmcevoy.http.values.ValueWriters;
import com.bradmcevoy.http.webdav.PropertyMap.StandardProperty;
import com.ettrema.http.report.Report;
import com.ettrema.http.report.ReportHandler;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Defines the methods and properties that make up the webdav protocol.
*
* We've been a little pragmatic about what the webdav protocol actually is. It
* generally doesnt include things defined in subsequent protocols (RFC's), but where
* something is frequently used by other protocols (like REPORT) or is very tightly
* couple with normal webdav operations (like quota checking) you'll find it here
*
*
* @author brad
*/
public class WebDavProtocol implements HttpExtension, PropertySource {
private static final Logger log = LoggerFactory.getLogger( WebDavProtocol.class );
public static final String NS_DAV = "DAV:";
private final Set<Handler> handlers;
private final Map<String, Report> reports;
private final ResourceTypeHelper resourceTypeHelper;
private final QuotaDataAccessor quotaDataAccessor;
private final PropertyMap propertyMap;
private final List<PropertySource> propertySources;
private final ETagGenerator eTagGenerator;
private DisplayNameFormatter displayNameFormatter = new DefaultDisplayNameFormatter();
//private DisplayNameFormatter displayNameFormatter = new CdataDisplayNameFormatter( new DefaultDisplayNameFormatter());
// public WebDavProtocol( Set<Handler> handlers ) {
// this.handlers = handlers;
// reports = new HashMap<String, Report>();
// }
public WebDavProtocol( WebDavResponseHandler responseHandler, HandlerHelper handlerHelper ) {
this( responseHandler, handlerHelper, new WebDavResourceTypeHelper() );
}
public WebDavProtocol( WebDavResponseHandler responseHandler, HandlerHelper handlerHelper, ResourceTypeHelper resourceTypeHelper ) {
this( handlerHelper, resourceTypeHelper, responseHandler, PropertySourceUtil.createDefaultSources( resourceTypeHelper ) );
}
public WebDavProtocol( HandlerHelper handlerHelper, ResourceTypeHelper resourceTypeHelper, WebDavResponseHandler responseHandler, List<PropertySource> extraPropertySources ) {
this( handlerHelper, resourceTypeHelper, responseHandler, extraPropertySources, new DefaultQuotaDataAccessor() );
}
public WebDavProtocol( HandlerHelper handlerHelper, ResourceTypeHelper resourceTypeHelper, WebDavResponseHandler responseHandler, List<PropertySource> extraPropertySources, QuotaDataAccessor quotaDataAccessor ) {
this(handlerHelper, resourceTypeHelper, responseHandler, extraPropertySources, quotaDataAccessor, null);
}
public WebDavProtocol( HandlerHelper handlerHelper, ResourceTypeHelper resourceTypeHelper, WebDavResponseHandler responseHandler, List<PropertySource> propertySources, QuotaDataAccessor quotaDataAccessor, PropPatchSetter patchSetter ) {
this.eTagGenerator = new DefaultETagGenerator();
handlers = new HashSet<Handler>();
this.resourceTypeHelper = resourceTypeHelper;
this.quotaDataAccessor = quotaDataAccessor;
this.propertyMap = new PropertyMap( WebDavProtocol.NS_DAV );
log.info( "resourceTypeHelper: " + resourceTypeHelper.getClass() );
log.info( "quotaDataAccessor: " + quotaDataAccessor.getClass() );
propertyMap.add( new ContentLengthPropertyWriter() );
propertyMap.add( new ContentTypePropertyWriter() );
propertyMap.add( new CreationDatePropertyWriter("getcreated") );
propertyMap.add( new CreationDatePropertyWriter("creationdate") );
propertyMap.add( new DisplayNamePropertyWriter() );
propertyMap.add( new LastModifiedDatePropertyWriter() );
propertyMap.add( new ResourceTypePropertyWriter() );
propertyMap.add( new EtagPropertyWriter() );
propertyMap.add( new SupportedLockPropertyWriter() );
propertyMap.add( new LockDiscoveryPropertyWriter() );
propertyMap.add( new MSIsCollectionPropertyWriter() );
propertyMap.add( new MSIsReadOnlyPropertyWriter() );
propertyMap.add( new MSNamePropertyWriter() );
propertyMap.add( new QuotaAvailableBytesPropertyWriter() );
propertyMap.add( new QuotaUsedBytesPropertyWriter() );
propertyMap.add( new SupportedReportSetProperty() );
ResourceHandlerHelper resourceHandlerHelper = new ResourceHandlerHelper( handlerHelper, responseHandler );
// note valuewriters is also used in DefaultWebDavResponseHandler
// if using non-default configuration you should inject the same instance into there
// and here
ValueWriters valueWriters = new ValueWriters();
log.debug( "provided property sources: " + propertySources.size() );
this.propertySources = propertySources;
log.debug( "adding webdav as a property source" );
addPropertySource( this );
if( patchSetter == null ) {
log.info("creating default patcheSetter: " + PropertySourcePatchSetter.class);
patchSetter = new PropertySourcePatchSetter( propertySources, valueWriters );
}
handlers.add( new PropFindHandler( resourceHandlerHelper, resourceTypeHelper, responseHandler, propertySources ) );
handlers.add( new MkColHandler( responseHandler, handlerHelper ) );
handlers.add( new PropPatchHandler( resourceHandlerHelper, responseHandler, patchSetter ) );
handlers.add( new CopyHandler( responseHandler, handlerHelper, resourceHandlerHelper ) );
handlers.add( new LockHandler( responseHandler, handlerHelper ) );
handlers.add( new UnlockHandler( resourceHandlerHelper, responseHandler ) );
handlers.add( new MoveHandler( responseHandler, handlerHelper, resourceHandlerHelper ) );
// Reports are added by other protocols via addReport
reports = new HashMap<String, Report>();
handlers.add( new ReportHandler( responseHandler, resourceHandlerHelper, reports ) );
}
public List<PropertySource> getPropertySources() {
return Collections.unmodifiableList( propertySources );
}
public void addPropertySource( PropertySource ps ) {
propertySources.add( ps );
log.debug( "adding property source: " + ps.getClass() + " new size: " + propertySources.size() );
}
public void addReport( Report report ) {
this.reports.put( report.getName(), report );
}
public Set<Handler> getHandlers() {
return Collections.unmodifiableSet( handlers );
}
/**
* Used as a marker to generate supported locks element in propfind responses
*
* See SupportedLockValueWriter
*/
public static class SupportedLocks {
}
public Object getProperty( QName name, Resource r ) {
Object o = propertyMap.getProperty( name, r );
return o;
}
public void setProperty( QName name, Object value, Resource r ) {
throw new UnsupportedOperationException( "Not supported. Standard webdav properties are not writable" );
}
public PropertyMetaData getPropertyMetaData( QName name, Resource r ) {
PropertyMetaData propertyMetaData= propertyMap.getPropertyMetaData( name, r );
return propertyMetaData;
}
public void clearProperty( QName name, Resource r ) {
throw new UnsupportedOperationException( "Not supported. Standard webdav properties are not writable" );
}
public List<QName> getAllPropertyNames( Resource r ) {
return propertyMap.getAllPropertyNames( r );
}
/**
* Generates the displayname element text. By default is a CdataDisplayNameFormatter
* wrapping a DefaultDisplayNameFormatter so that extended character sets
* are supported
*
* @return
*/
public DisplayNameFormatter getDisplayNameFormatter() {
return displayNameFormatter;
}
public void setDisplayNameFormatter( DisplayNameFormatter displayNameFormatter ) {
this.displayNameFormatter = displayNameFormatter;
}
class DisplayNamePropertyWriter implements StandardProperty<String> {
public String getValue( PropFindableResource res ) {
return displayNameFormatter.formatDisplayName( res );
}
public String fieldName() {
return "displayname";
}
public Class<String> getValueClass() {
return String.class;
}
}
class CreationDatePropertyWriter implements StandardProperty<Date> {
private final String fieldName;
public CreationDatePropertyWriter( String fieldName ) {
this.fieldName = fieldName;
}
public String fieldName() {
return fieldName;
}
public Date getValue( PropFindableResource res ) {
return res.getModifiedDate();
}
public Class<Date> getValueClass() {
return Date.class;
}
}
class LastModifiedDatePropertyWriter implements StandardProperty<Date> {
public String fieldName() {
return "getlastmodified";
}
public Date getValue( PropFindableResource res ) {
return res.getModifiedDate();
}
public Class<Date> getValueClass() {
return Date.class;
}
}
class ResourceTypePropertyWriter implements StandardProperty<List<QName>> {
public List<QName> getValue( PropFindableResource res ) {
log.trace( "ResourceTypePropertyWriter:getValue" );
return resourceTypeHelper.getResourceTypes( res );
}
public String fieldName() {
return "resourcetype";
}
public Class getValueClass() {
return List.class;
}
}
class ContentTypePropertyWriter implements StandardProperty<String> {
public String getValue( PropFindableResource res ) {
if( res instanceof GetableResource ) {
GetableResource getable = (GetableResource) res;
return getable.getContentType( null );
} else {
return "";
}
}
public String fieldName() {
return "getcontenttype";
}
public Class getValueClass() {
return String.class;
}
}
class ContentLengthPropertyWriter implements StandardProperty<Long> {
public Long getValue( PropFindableResource res ) {
if( res instanceof GetableResource ) {
GetableResource getable = (GetableResource) res;
Long l = getable.getContentLength();
return l;
} else {
return null;
}
}
public String fieldName() {
return "getcontentlength";
}
public Class getValueClass() {
return Long.class;
}
}
class QuotaUsedBytesPropertyWriter implements StandardProperty<Long> {
public Long getValue( PropFindableResource res ) {
return quotaDataAccessor.getQuotaUsed( res );
}
public String fieldName() {
return "quota-used-bytes";
}
public Class getValueClass() {
return Long.class;
}
}
class QuotaAvailableBytesPropertyWriter implements StandardProperty<Long> {
public Long getValue( PropFindableResource res ) {
return quotaDataAccessor.getQuotaAvailable( res );
}
public String fieldName() {
return "quota-available-bytes";
}
public Class getValueClass() {
return Long.class;
}
}
class EtagPropertyWriter implements StandardProperty<String> {
public String getValue( PropFindableResource res ) {
String etag = eTagGenerator.generateEtag( res );
return etag;
}
public String fieldName() {
return "getetag";
}
public Class getValueClass() {
return String.class;
}
}
// <D:supportedlock/><D:lockdiscovery/>
class LockDiscoveryPropertyWriter implements StandardProperty<LockToken> {
public LockToken getValue( PropFindableResource res ) {
if( !( res instanceof LockableResource ) ) return null;
LockableResource lr = (LockableResource) res;
LockToken token = lr.getCurrentLock();
return token;
}
public String fieldName() {
return "supportedlock";
}
public Class getValueClass() {
return LockToken.class;
}
}
class SupportedLockPropertyWriter implements StandardProperty<SupportedLocks> {
public SupportedLocks getValue( PropFindableResource res ) {
if( res instanceof LockableResource ) {
return new SupportedLocks();
} else {
return null;
}
}
public String fieldName() {
return "supportedlock";
}
public Class getValueClass() {
return SupportedLocks.class;
}
}
// MS specific fields
class MSNamePropertyWriter extends DisplayNamePropertyWriter {
@Override
public String fieldName() {
return "name";
}
}
class MSIsCollectionPropertyWriter implements StandardProperty<Boolean> {
public String fieldName() {
return "iscollection";
}
public Boolean getValue( PropFindableResource res ) {
return ( res instanceof CollectionResource );
}
public Class getValueClass() {
return Boolean.class;
}
}
class MSIsReadOnlyPropertyWriter implements StandardProperty<Boolean> {
public String fieldName() {
return "isreadonly";
}
public Boolean getValue( PropFindableResource res ) {
return !( res instanceof PutableResource );
}
public Class getValueClass() {
return Boolean.class;
}
}
class SupportedReportSetProperty implements StandardProperty<SupportedReportSetList> {
public String fieldName() {
return "supported-report-set";
}
public SupportedReportSetList getValue( PropFindableResource res ) {
SupportedReportSetList reportSet = new SupportedReportSetList();
for (String reportName : reports.keySet()) {
reportSet.add(reportName);
}
return reportSet;
}
public Class getValueClass() {
return SupportedReportSetList.class;
}
}
protected void sendStringProp( XmlWriter writer, String name, String value ) {
String s = value;
if( s == null ) {
writer.writeProperty( null, name );
} else {
writer.writeProperty( null, name, s );
}
}
void sendDateProp( XmlWriter writer, String name, Date date ) {
sendStringProp( writer, name, ( date == null ? null : DateUtils.formatDate( date ) ) );
}
}