package com.bradmcevoy.http.webdav;
import com.bradmcevoy.http.*;
import com.bradmcevoy.http.exceptions.BadRequestException;
import com.bradmcevoy.http.exceptions.ConflictException;
import com.bradmcevoy.http.exceptions.LockedException;
import com.bradmcevoy.http.exceptions.NotAuthorizedException;
import com.bradmcevoy.http.exceptions.PreConditionFailedException;
import org.apache.commons.io.output.ByteArrayOutputStream;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import com.bradmcevoy.common.Path;
import com.bradmcevoy.http.Request.Method;
import com.bradmcevoy.http.Response.Status;
/**
* Note that this is both a new entity handler and an existing entity handler
*
* @author brad
*/
public class LockHandler implements ResourceHandler {
private static final Logger log = LoggerFactory.getLogger( LockHandler.class );
private final WebDavResponseHandler responseHandler;
private final HandlerHelper handlerHelper;
private LockWriterHelper lockWriterHelper;
public LockHandler( WebDavResponseHandler responseHandler, HandlerHelper handlerHelper ) {
this.responseHandler = responseHandler;
this.handlerHelper = handlerHelper;
lockWriterHelper = new LockWriterHelper();
}
public LockWriterHelper getLockWriterHelper() {
return lockWriterHelper;
}
public void setLockWriterHelper( LockWriterHelper lockWriterHelper ) {
this.lockWriterHelper = lockWriterHelper;
}
public void processResource( HttpManager manager, Request request, Response response, Resource r ) throws NotAuthorizedException, ConflictException, BadRequestException {
throw new UnsupportedOperationException( "Not supported yet." );
}
public String[] getMethods() {
return new String[]{Method.LOCK.code};
}
public void process( HttpManager manager, Request request, Response response ) throws NotAuthorizedException {
if( !handlerHelper.checkExpects( responseHandler, request, response ) ) {
return;
}
String host = request.getHostHeader();
String url = HttpManager.decodeUrl( request.getAbsolutePath() );
// Find a resource if it exists
Resource r = manager.getResourceFactory().getResource( host, url );
if( r != null ) {
log.debug( "locking existing resource: " + r.getName() );
processExistingResource( manager, request, response, r );
} else {
log.debug( "lock target doesnt exist, attempting lock null.." );
processNonExistingResource( manager, request, response, host, url );
}
}
protected void processExistingResource( HttpManager manager, Request request, Response response, Resource resource ) throws NotAuthorizedException {
if( handlerHelper.isNotCompatible( resource, request.getMethod()) || !isCompatible( resource ) ) {
responseHandler.respondMethodNotImplemented( resource, response, request );
return;
}
if( !handlerHelper.checkAuthorisation( manager, resource, request ) ) {
responseHandler.respondUnauthorised( resource, response, request );
return;
}
handlerHelper.checkExpects( responseHandler, request, response );
LockableResource r = (LockableResource) resource;
LockTimeout timeout = LockTimeout.parseTimeout( request );
String ifHeader = request.getIfHeader();
response.setContentTypeHeader( Response.XML );
if( ifHeader == null || ifHeader.length() == 0 ) {
processNewLock( manager, request, response, r, timeout );
} else {
processRefresh( manager, request, response, r, timeout, ifHeader );
}
}
/**
* (from the spec)
* 7.4 Write Locks and Null Resources
*
* It is possible to assert a write lock on a null resource in order to lock the name.
*
* A write locked null resource, referred to as a lock-null resource, MUST respond with
* a 404 (Not Found) or 405 (Method Not Allowed) to any HTTP/1.1 or DAV methods except
* for PUT, MKCOL, OPTIONS, PROPFIND, LOCK, and UNLOCK. A lock-null resource MUST appear
* as a member of its parent collection. Additionally the lock-null resource MUST have
* defined on it all mandatory DAV properties. Most of these properties, such as all
* the get* properties, will have no value as a lock-null resource does not support the GET method.
* Lock-Null resources MUST have defined values for lockdiscovery and supportedlock properties.
*
* Until a method such as PUT or MKCOL is successfully executed on the lock-null resource the resource
* MUST stay in the lock-null state. However, once a PUT or MKCOL is successfully executed on
* a lock-null resource the resource ceases to be in the lock-null state.
*
* If the resource is unlocked, for any reason, without a PUT, MKCOL, or
* similar method having been successfully executed upon it then the resource
* MUST return to the null state.
*
*
* @param manager
* @param request
* @param response
* @param host
* @param url
*/
private void processNonExistingResource( HttpManager manager, Request request, Response response, String host, String url ) throws NotAuthorizedException {
String name;
Path parentPath = Path.path( url );
name = parentPath.getName();
parentPath = parentPath.getParent();
url = parentPath.toString();
Resource r = manager.getResourceFactory().getResource( host, url );
if( r != null ) {
if( !handlerHelper.checkAuthorisation( manager, r, request ) ) {
responseHandler.respondUnauthorised( r, response, request );
return;
} else {
processCreateAndLock( manager, request, response, r, name );
}
} else {
log.debug( "couldnt find parent to execute lock-null, returning not found" );
//respondNotFound(response,request);
response.setStatus( Status.SC_CONFLICT );
}
}
private void processCreateAndLock( HttpManager manager, Request request, Response response, Resource parentResource, String name ) throws NotAuthorizedException {
if( parentResource instanceof LockingCollectionResource ) {
log.debug( "parent supports lock-null. doing createAndLock" );
LockingCollectionResource lockingParent = (LockingCollectionResource) parentResource;
LockTimeout timeout = LockTimeout.parseTimeout( request );
response.setContentTypeHeader( Response.XML );
LockInfo lockInfo;
try {
lockInfo = LockInfo.parseLockInfo( request );
} catch( SAXException ex ) {
throw new RuntimeException( "Exception reading request body", ex );
} catch( IOException ex ) {
throw new RuntimeException( "Exception reading request body", ex );
}
// TODO: this should be refactored to return a LockResult as for existing entities
log.debug( "Creating lock on unmapped resource: " + name );
LockToken tok = lockingParent.createAndLock( name, timeout, lockInfo );
response.setStatus( Status.SC_CREATED );
response.setLockTokenHeader( "<opaquelocktoken:" + tok.tokenId + ">" ); // spec says to set response header. See 8.10.1
respondWithToken( tok, request, response );
} else {
log.debug( "parent does not support lock-null, respondong method not allowed" );
responseHandler.respondMethodNotImplemented( parentResource, response, request );
}
}
public boolean isCompatible( Resource handler ) {
return handler instanceof LockableResource;
}
protected void processNewLock( HttpManager milton, Request request, Response response, LockableResource r, LockTimeout timeout ) throws NotAuthorizedException {
LockInfo lockInfo;
try {
lockInfo = LockInfo.parseLockInfo( request );
} catch( SAXException ex ) {
throw new RuntimeException( "Exception reading request body", ex );
} catch( IOException ex ) {
throw new RuntimeException( "Exception reading request body", ex );
}
if( handlerHelper.isLockedOut( request, r ) ) {
this.responseHandler.respondLocked( request, response, r );
return;
}
log.debug( "locking: " + r.getName() );
LockResult result;
try {
result = r.lock( timeout, lockInfo );
} catch( PreConditionFailedException ex ) {
responseHandler.respondPreconditionFailed( request, response, r );
return ;
} catch( LockedException ex ) {
responseHandler.respondLocked( request, response, r );
return ;
}
if( result.isSuccessful() ) {
LockToken tok = result.getLockToken();
log.debug( "..locked ok: " + tok.tokenId );
response.setLockTokenHeader( "<opaquelocktoken:" + tok.tokenId + ">" ); // spec says to set response header. See 8.10.1
respondWithToken( tok, request, response );
} else {
responseWithLockFailure( result, request, response );
}
}
protected void processRefresh( HttpManager milton, Request request, Response response, LockableResource r, LockTimeout timeout, String ifHeader ) throws NotAuthorizedException {
String token = parseToken( ifHeader );
log.debug( "refreshing lock: " + token );
LockResult result;
try {
result = r.refreshLock( token );
} catch( PreConditionFailedException ex ) {
responseHandler.respondPreconditionFailed( request, response, r );
return ;
}
if( result.isSuccessful() ) {
LockToken tok = result.getLockToken();
respondWithToken( tok, request, response );
} else {
responseWithLockFailure( result, request, response );
}
}
protected void respondWithToken( LockToken tok, Request request, Response response ) {
response.setStatus( Status.SC_OK );
ByteArrayOutputStream out = new ByteArrayOutputStream();
XmlWriter writer = new XmlWriter( out );
writer.writeXMLHeader();
writer.open( "D:prop xmlns:D=\"DAV:\"" );
writer.newLine();
writer.open( "D:lockdiscovery" );
writer.newLine();
writer.open( "D:activelock" );
writer.newLine();
lockWriterHelper.appendType( writer, tok.info.type );
lockWriterHelper.appendScope( writer, tok.info.scope );
lockWriterHelper.appendDepth( writer, tok.info.depth );
lockWriterHelper.appendOwner( writer, tok.info.lockedByUser );
lockWriterHelper.appendTimeout( writer, tok.timeout.getSeconds() );
lockWriterHelper.appendTokenId( writer, tok.tokenId );
lockWriterHelper.appendRoot( writer, request.getAbsoluteUrl() );
writer.close( "D:activelock" );
writer.close( "D:lockdiscovery" );
writer.close( "D:prop" );
writer.flush();
log.debug( "lock response: " + out.toString() );
try {
response.getOutputStream().write( out.toByteArray() );
} catch( IOException ex ) {
log.warn( "exception writing to outputstream", ex );
}
// response.close();
}
static String parseToken( String ifHeader ) {
String token = ifHeader;
int pos = token.indexOf( ":" );
if( pos >= 0 ) {
token = token.substring( pos + 1 );
pos = token.indexOf( ">" );
if( pos >= 0 ) {
token = token.substring( 0, pos );
}
}
return token;
}
private void responseWithLockFailure( LockResult result, Request request, Response response ) {
response.setStatus( result.getFailureReason().status );
}
}