View Javadoc

1   package com.bradmcevoy.http.webdav;
2   
3   import com.bradmcevoy.http.*;
4   import com.bradmcevoy.http.exceptions.BadRequestException;
5   import com.bradmcevoy.http.exceptions.ConflictException;
6   import com.bradmcevoy.http.exceptions.LockedException;
7   import com.bradmcevoy.http.exceptions.NotAuthorizedException;
8   import com.bradmcevoy.http.exceptions.PreConditionFailedException;
9   import org.apache.commons.io.output.ByteArrayOutputStream;
10  import java.io.IOException;
11  
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  import org.xml.sax.SAXException;
15  
16  import com.bradmcevoy.common.Path;
17  import com.bradmcevoy.http.Request.Method;
18  import com.bradmcevoy.http.Response.Status;
19  
20  /**
21   * Note that this is both a new entity handler and an existing entity handler
22   * 
23   * @author brad
24   */
25  public class LockHandler implements ResourceHandler {
26  
27      private static final Logger log = LoggerFactory.getLogger( LockHandler.class );
28      private final WebDavResponseHandler responseHandler;
29      private final HandlerHelper handlerHelper;
30  
31      private LockWriterHelper lockWriterHelper;
32  
33      public LockHandler( WebDavResponseHandler responseHandler, HandlerHelper handlerHelper ) {
34          this.responseHandler = responseHandler;
35          this.handlerHelper = handlerHelper;
36          lockWriterHelper = new LockWriterHelper();
37      }
38  
39      public LockWriterHelper getLockWriterHelper() {
40          return lockWriterHelper;
41      }
42  
43      public void setLockWriterHelper( LockWriterHelper lockWriterHelper ) {
44          this.lockWriterHelper = lockWriterHelper;
45      }
46  
47  
48      public void processResource( HttpManager manager, Request request, Response response, Resource r ) throws NotAuthorizedException, ConflictException, BadRequestException {
49          throw new UnsupportedOperationException( "Not supported yet." );
50      }
51  
52      public String[] getMethods() {
53          return new String[]{Method.LOCK.code};
54      }
55  
56      @Override
57      public void process( HttpManager manager, Request request, Response response ) throws NotAuthorizedException {
58          if( !handlerHelper.checkExpects( responseHandler, request, response ) ) {
59              return;
60          }
61  
62          String host = request.getHostHeader();
63          String url = HttpManager.decodeUrl( request.getAbsolutePath() );
64  
65          // Find a resource if it exists
66          Resource r = manager.getResourceFactory().getResource( host, url );
67          if( r != null ) {
68              log.debug( "locking existing resource: " + r.getName() );
69              processExistingResource( manager, request, response, r );
70          } else {
71              log.debug( "lock target doesnt exist, attempting lock null.." );
72              processNonExistingResource( manager, request, response, host, url );
73          }
74      }
75  
76      protected void processExistingResource( HttpManager manager, Request request, Response response, Resource resource ) throws NotAuthorizedException {
77          if( handlerHelper.isNotCompatible( resource, request.getMethod()) || !isCompatible( resource ) ) {
78              responseHandler.respondMethodNotImplemented( resource, response, request );
79              return;
80          }
81          if( !handlerHelper.checkAuthorisation( manager, resource, request ) ) {
82              responseHandler.respondUnauthorised( resource, response, request );
83              return;
84          }
85  
86          handlerHelper.checkExpects( responseHandler, request, response );
87  
88          LockableResource r = (LockableResource) resource;
89          LockTimeout timeout = LockTimeout.parseTimeout( request );
90          String ifHeader = request.getIfHeader();
91          response.setContentTypeHeader( Response.XML );
92          if( ifHeader == null || ifHeader.length() == 0 ) {
93              processNewLock( manager, request, response, r, timeout );
94          } else {
95              processRefresh( manager, request, response, r, timeout, ifHeader );
96          }
97      }
98  
99      /**
100      * (from the spec)
101      * 7.4 Write Locks and Null Resources
102      *
103      * It is possible to assert a write lock on a null resource in order to lock the name.
104      *
105      * A write locked null resource, referred to as a lock-null resource, MUST respond with
106      * a 404 (Not Found) or 405 (Method Not Allowed) to any HTTP/1.1 or DAV methods except
107      * for PUT, MKCOL, OPTIONS, PROPFIND, LOCK, and UNLOCK. A lock-null resource MUST appear
108      * as a member of its parent collection. Additionally the lock-null resource MUST have
109      * defined on it all mandatory DAV properties. Most of these properties, such as all
110      * the get* properties, will have no value as a lock-null resource does not support the GET method.
111      * Lock-Null resources MUST have defined values for lockdiscovery and supportedlock properties.
112      *
113      * Until a method such as PUT or MKCOL is successfully executed on the lock-null resource the resource 
114      * MUST stay in the lock-null state. However, once a PUT or MKCOL is successfully executed on
115      * a lock-null resource the resource ceases to be in the lock-null state.
116      *
117      * If the resource is unlocked, for any reason, without a PUT, MKCOL, or 
118      * similar method having been successfully executed upon it then the resource
119      * MUST return to the null state.
120      *
121      *
122      * @param manager
123      * @param request
124      * @param response
125      * @param host
126      * @param url
127      */
128     private void processNonExistingResource( HttpManager manager, Request request, Response response, String host, String url ) throws NotAuthorizedException {
129         String name;
130 
131         Path parentPath = Path.path( url );
132         name = parentPath.getName();
133         parentPath = parentPath.getParent();
134         url = parentPath.toString();
135 
136         Resource r = manager.getResourceFactory().getResource( host, url );
137         if( r != null ) {
138             if( !handlerHelper.checkAuthorisation( manager, r, request ) ) {
139                 responseHandler.respondUnauthorised( r, response, request );
140                 return;
141             } else {
142                 processCreateAndLock( manager, request, response, r, name );
143             }
144         } else {
145             log.debug( "couldnt find parent to execute lock-null, returning not found" );
146             //respondNotFound(response,request);
147             response.setStatus( Status.SC_CONFLICT );
148 
149         }
150     }
151 
152     private void processCreateAndLock( HttpManager manager, Request request, Response response, Resource parentResource, String name ) throws NotAuthorizedException {
153         if( parentResource instanceof LockingCollectionResource ) {
154             log.debug( "parent supports lock-null. doing createAndLock" );
155             LockingCollectionResource lockingParent = (LockingCollectionResource) parentResource;
156             LockTimeout timeout = LockTimeout.parseTimeout( request );
157             response.setContentTypeHeader( Response.XML );
158 
159             LockInfo lockInfo;
160             try {
161                 lockInfo = LockInfo.parseLockInfo( request );
162             } catch( SAXException ex ) {
163                 throw new RuntimeException( "Exception reading request body", ex );
164             } catch( IOException ex ) {
165                 throw new RuntimeException( "Exception reading request body", ex );
166             }
167 
168             // TODO: this should be refactored to return a LockResult as for existing entities
169 
170             log.debug( "Creating lock on unmapped resource: " + name );
171             LockToken tok = lockingParent.createAndLock( name, timeout, lockInfo );
172             if( tok == null ) {
173                 throw new RuntimeException( "createAndLock returned null, from resource of type: " + lockingParent.getClass().getCanonicalName());
174             }
175             response.setStatus( Status.SC_CREATED );
176             response.setLockTokenHeader( "<opaquelocktoken:" + tok.tokenId + ">" );  // spec says to set response header. See 8.10.1
177             respondWithToken( tok, request, response );
178 
179         } else {
180             log.debug( "parent does not support lock-null, respondong method not allowed" );
181             responseHandler.respondMethodNotImplemented( parentResource, response, request );
182         }
183     }
184 
185     @Override
186     public boolean isCompatible( Resource handler ) {
187         return handler instanceof LockableResource;
188     }
189 
190     protected void processNewLock( HttpManager milton, Request request, Response response, LockableResource r, LockTimeout timeout ) throws NotAuthorizedException {
191         LockInfo lockInfo;
192         try {
193             lockInfo = LockInfo.parseLockInfo( request );
194         } catch( SAXException ex ) {
195             throw new RuntimeException( "Exception reading request body", ex );
196         } catch( IOException ex ) {
197             throw new RuntimeException( "Exception reading request body", ex );
198         }
199 
200         if( handlerHelper.isLockedOut( request, r ) ) {
201             this.responseHandler.respondLocked( request, response, r );
202             return;
203         }
204 
205         log.debug( "locking: " + r.getName() );
206         LockResult result;
207         try {
208             result = r.lock( timeout, lockInfo );
209         } catch( PreConditionFailedException ex ) {
210             responseHandler.respondPreconditionFailed( request, response, r );
211             return ;
212         } catch( LockedException ex ) {
213             responseHandler.respondLocked( request, response, r );
214             return ;
215         }
216 
217         if( result.isSuccessful() ) {
218             LockToken tok = result.getLockToken();
219             log.debug( "..locked ok: " + tok.tokenId );
220             response.setLockTokenHeader( "<opaquelocktoken:" + tok.tokenId + ">" );  // spec says to set response header. See 8.10.1
221             respondWithToken( tok, request, response );
222         } else {
223             respondWithLockFailure( result, request, response );
224         }
225     }
226 
227     protected void processRefresh( HttpManager milton, Request request, Response response, LockableResource r, LockTimeout timeout, String ifHeader ) throws NotAuthorizedException {
228         String token = parseToken( ifHeader );
229         log.debug( "refreshing lock: " + token );
230         LockResult result;
231         try {
232             result = r.refreshLock( token );
233         } catch( PreConditionFailedException ex ) {
234             responseHandler.respondPreconditionFailed( request, response, r );
235             return ;
236         }
237         if( result.isSuccessful() ) {
238             LockToken tok = result.getLockToken();
239             respondWithToken( tok, request, response );
240         } else {
241             respondWithLockFailure( result, request, response );
242         }
243     }
244 
245     protected void respondWithToken( LockToken tok, Request request, Response response ) {
246         response.setStatus( Status.SC_OK );
247         ByteArrayOutputStream out = new ByteArrayOutputStream();
248         XmlWriter writer = new XmlWriter( out );
249         writer.writeXMLHeader();
250         writer.open( "D:prop  xmlns:D=\"DAV:\"" );
251         writer.newLine();
252         writer.open( "D:lockdiscovery" );
253         writer.newLine();
254         writer.open( "D:activelock" );
255         writer.newLine();
256         lockWriterHelper.appendType( writer, tok.info.type );
257         lockWriterHelper.appendScope( writer, tok.info.scope );
258         lockWriterHelper.appendDepth( writer, tok.info.depth );
259         lockWriterHelper.appendOwner( writer, tok.info.lockedByUser );
260         lockWriterHelper.appendTimeout( writer, tok.timeout.getSeconds() );
261         lockWriterHelper.appendTokenId( writer, tok.tokenId );
262         lockWriterHelper.appendRoot( writer, request.getAbsoluteUrl() );
263         writer.close( "D:activelock" );
264         writer.close( "D:lockdiscovery" );
265         writer.close( "D:prop" );
266         writer.flush();
267 
268         log.debug( "lock response: " + out.toString() );
269         try {
270             response.getOutputStream().write( out.toByteArray() );
271         } catch( IOException ex ) {
272             log.warn( "exception writing to outputstream", ex );
273         }
274 //        response.close();
275 
276     }
277 
278     static String parseToken( String ifHeader ) {
279         String token = ifHeader;
280         int pos = token.indexOf( ":" );
281         if( pos >= 0 ) {
282             token = token.substring( pos + 1 );
283             pos = token.indexOf( ">" );
284             if( pos >= 0 ) {
285                 token = token.substring( 0, pos );
286             }
287         }
288         return token;
289     }
290 
291 
292     private void respondWithLockFailure( LockResult result, Request request, Response response ) {
293         log.info("respondWithLockFailure: " + result.getFailureReason().name());
294         response.setStatus( result.getFailureReason().status );
295 
296     }
297 }