View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.webdav;
14  
15  import org.abstracthorizon.danube.adapter.Adaptable;
16  import org.abstracthorizon.danube.http.BaseReflectionHTTPConnectionHandler;
17  import org.abstracthorizon.danube.http.HTTPBufferedInputStream;
18  import org.abstracthorizon.danube.http.HTTPConnection;
19  import org.abstracthorizon.danube.http.Status;
20  import org.abstracthorizon.danube.http.util.IOUtils;
21  import org.abstracthorizon.danube.http.util.Ranges;
22  import org.abstracthorizon.danube.support.RuntimeIOException;
23  import org.abstracthorizon.danube.support.logging.LoggingConnection;
24  import org.abstracthorizon.danube.webdav.lock.Lock;
25  import org.abstracthorizon.danube.webdav.lock.LockingMechanism;
26  import org.abstracthorizon.danube.webdav.util.CollectionHTMLRenderer;
27  import org.abstracthorizon.danube.webdav.util.Depth;
28  import org.abstracthorizon.danube.webdav.util.IF;
29  import org.abstracthorizon.danube.webdav.util.SimpleHTMLCollectionRenderer;
30  import org.abstracthorizon.danube.webdav.util.Timeout;
31  import org.abstracthorizon.danube.webdav.util.URIUtils;
32  import org.abstracthorizon.danube.webdav.xml.WebDAVXMLHandler;
33  import org.abstracthorizon.danube.webdav.xml.dav.RequestProp;
34  import org.abstracthorizon.danube.webdav.xml.dav.request.LockInfo;
35  import org.abstracthorizon.danube.webdav.xml.dav.request.PropFind;
36  import org.abstracthorizon.danube.webdav.xml.dav.request.PropertyBehavior;
37  import org.abstracthorizon.danube.webdav.xml.dav.request.PropertyUpdate;
38  import org.abstracthorizon.danube.webdav.xml.dav.request.properties.RequestProperty;
39  import org.abstracthorizon.danube.webdav.xml.dav.response.MultiStatus;
40  import org.abstracthorizon.danube.webdav.xml.dav.response.Propstat;
41  import org.abstracthorizon.danube.webdav.xml.dav.response.Response;
42  import org.abstracthorizon.danube.webdav.xml.dav.response.properties.LockDiscovery;
43  import org.abstracthorizon.danube.webdav.xml.dav.response.properties.ResponseProperty;
44  
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.io.OutputStream;
48  import java.util.ArrayList;
49  import java.util.Arrays;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.StringTokenizer;
54  
55  import javax.xml.parsers.ParserConfigurationException;
56  import javax.xml.parsers.SAXParser;
57  import javax.xml.parsers.SAXParserFactory;
58  
59  import org.xml.sax.InputSource;
60  import org.xml.sax.SAXException;
61  import org.xml.sax.XMLReader;
62  
63  /**
64   * Base WebDAV resource connection handler. This class uses supplied {@link ResourceAdapter}
65   * to read and write resource to/from.
66   *
67   * @author Daniel Sendula
68   */
69  public class BaseWebDAVResourceConnectionHandler extends BaseReflectionHTTPConnectionHandler {
70  
71      /** Status 423 Locked */
72      public static final Status STATUS_LOCKED = new Status("423", "Locked");
73  
74      /** Status 424 Failed Dependency */
75      public static final Status STATUS_FAILED_DEPENDENCY = new Status("424", "Failed Dependency");
76  
77      /** Resource adapter to be used */
78      protected ResourceAdapter adapter;
79  
80      /** Internal buffer size */
81      protected int bufferSize = 2048;
82  
83      /** Renderer of collection of items if GET method is invoked on a collection resource */
84      protected CollectionHTMLRenderer collectionRenderer = new SimpleHTMLCollectionRenderer();
85  
86      /** Flag denoting will this class allow resources to be changed or not */
87      protected boolean readOnly = false;
88  
89      /**
90       * Constructor
91       */
92      public BaseWebDAVResourceConnectionHandler() {
93      }
94  
95      /**
96       * Constructor
97       *
98       * @param webDAVAdapter web dav adapter
99       */
100     public BaseWebDAVResourceConnectionHandler(ResourceAdapter webDAVAdapter) {
101         setWebDAVResourceAdapter(webDAVAdapter);
102     }
103 
104     /**
105      * Returns WebDAV resource adapter
106      *
107      * @return WebDAV resource
108      */
109     public ResourceAdapter getWebDAVResourceAdapter() {
110         return adapter;
111     }
112 
113     /**
114      * Sets WebDAV resource adapter
115      *
116      * @param webDAVAdapter WebDAV resource
117      */
118     public void setWebDAVResourceAdapter(ResourceAdapter webDAVAdapter) {
119         this.adapter = webDAVAdapter;
120     }
121 
122     /**
123      * Returns collection HTML renderer
124      * @return collection HTML renderer
125      */
126     public CollectionHTMLRenderer getCollectionHTMLRenderer() {
127         return collectionRenderer;
128     }
129 
130     /**
131      * Sets collection HTML renderer
132      * @param collectionHTMLRenderer collection HTML renderer
133      */
134     public void setCollectionHTMLRenterer(CollectionHTMLRenderer collectionHTMLRenderer) {
135         this.collectionRenderer = collectionHTMLRenderer;
136     }
137 
138     /**
139      * Returns read only flag
140      * @return read only flag
141      */
142     public boolean isReadOnly() {
143         return readOnly;
144     }
145 
146     /**
147      * Sets read only flag
148      * @param readOnly read only flag
149      */
150     public void setReadOnly(boolean readOnly) {
151         this.readOnly = readOnly;
152     }
153 
154 
155     /**
156      * Returns a resource. This implementation obtains resource from the supplied
157      * adapter using component resource path as a resource's path.
158      *
159      * This method can be overriden if adapter is to cache resources or pre-process them
160      * in any way.
161      *
162      * @param httpConnection http connection
163      * @return resource
164      */
165     protected Object findResource(HTTPConnection httpConnection) {
166         return adapter.findResource(httpConnection.getComponentResourcePath());
167     }
168 
169     /**
170      * Caches methods for quick invocation. This implementation
171      * removes LOCK and UNLOCK method from the cache if adapter doesn't supply
172      * locking mechanism (lokcing mechanism is <code>null</code>).
173      *
174      */
175     @Override
176     protected void cacheMethods() {
177         super.cacheMethods();
178         if (adapter.getLockingMechanism() == null) {
179             cachedMethods.remove("LOCK");
180             cachedMethods.remove("UNLOCK");
181         }
182     }
183 
184     /**
185      * This method adds DAV: 1,2 header.
186      * @param httpConnection connection
187      */
188     @Override
189     public void methodOPTIONS(HTTPConnection httpConnection) {
190         super.methodOPTIONS(httpConnection);
191         httpConnection.getResponseHeaders().putOnly("DAV", "1,2");
192     }
193 
194     /**
195      * GET method implementation
196      *
197      * @param httpConnection connection
198      */
199     public void methodGET(HTTPConnection httpConnection) {
200         Object resource = findResource(httpConnection);
201         if (adapter.exists(resource)) {
202             Ranges ranges = collectRange(httpConnection);
203             if (adapter.isCollection(resource)) {
204                 if (ranges != null) {
205                     httpConnection.setResponseStatus(Status.UNSUPPORTED_MEDIA_TYPE);
206                 } else {
207                     collectionRenderer.render(httpConnection, adapter, resource);
208                 }
209             } else {
210                 transmitResource(httpConnection, resource, httpConnection, ranges);
211             }
212         } else {
213             httpConnection.setResponseStatus(Status.NOT_FOUND);
214         }
215     }
216 
217     /**
218      * Transmits resource.
219      *
220      * @param httpConnection connection
221      * @param resource resource to be used
222      * @param output adaptable to be checked for the output stream
223      * @param ranges ranges. Can be <code>null</code>.
224      */
225     protected void transmitResource(HTTPConnection httpConnection, Object resource, Adaptable output, Ranges ranges) {
226         long size = adapter.resourceLength(resource);
227         if ((ranges != null) && (size >= 0)) {
228             ranges.setSize(size);
229         }
230 
231         InputStream in = null;
232 
233         if (ranges == null) {
234             try {
235                 in = adapter.getInputStream(resource);
236             } catch (IOException ignore) {
237                 httpConnection.setResponseStatus(Status.FORBIDDEN);
238                 return;
239             }
240         } else if (!ranges.isMultiRange()) {
241             long from = ranges.getSingleRange().getFrom();
242             long to = ranges.getSingleRange().getTo();
243 
244             if ((from < 0) || (to >= size)) {
245                 httpConnection.setResponseStatus(Status.RANGE_NOT_SATISFIABLE);
246             } else {
247                 size = to - from + 1;
248                 try {
249                     in = adapter.getInpusStream(resource, from, size);
250                 } catch (IOException e) {
251                     httpConnection.setResponseStatus(Status.FORBIDDEN);
252                     return;
253                 }
254             }
255         } else {
256             // Multirange is not supported for upload
257             httpConnection.setResponseStatus(Status.UNSUPPORTED_MEDIA_TYPE);
258             return;
259         }
260 
261         if (in != null) {
262             try {
263                 boolean oldLoggingState = false;
264                 LoggingConnection loggingConnection = (LoggingConnection)httpConnection.adapt(LoggingConnection.class);
265                 if (loggingConnection != null) {
266                     oldLoggingState = loggingConnection.isLogging();
267                     loggingConnection.setLogging(false);
268                 }
269                 try {
270                     OutputStream out = (OutputStream)output.adapt(OutputStream.class);
271 
272                     int bufSize = bufferSize;
273                     if ((size > 0) && (bufSize > size)) {
274                         bufSize = (int)size;
275                     }
276                     byte[] buffer = new byte[bufSize];
277                     boolean ok = (size != 0);
278 
279                     while (ok) {
280                         int r = bufSize;
281                         if ((size > 0) && (r > size)) {
282                             r = (int)size;
283                         }
284                         r = in.read(buffer, 0, r);
285                         if (r <= 0) {
286                             ok = false;
287                         } else {
288                             out.write(buffer, 0, r);
289                             if (size >= 0) {
290                                 size = size - r;
291                                 if (size <= 0) {
292                                     ok = false;
293                                 }
294                             }
295                         }
296                     }
297                 } finally {
298                     if (loggingConnection != null) {
299                         loggingConnection.setLogging(oldLoggingState);
300                     }
301                     in.close();
302                 }
303             } catch (IOException e) {
304                 throw new RuntimeIOException(e);
305             }
306             if (ranges != null) {
307                 httpConnection.getResponseHeaders().putOnly("Content-Range", ranges.format());
308             }
309         }
310     }
311 
312     /**
313      * Implementation of the HEAD method.
314      *
315      * @param httpConnection connection
316      */
317     public void methodHEAD(HTTPConnection httpConnection) {
318         Object resource = findResource(httpConnection);
319         if (adapter.exists(resource)) {
320             Ranges ranges = collectRange(httpConnection);
321             long size = adapter.resourceLength(resource);
322             if (ranges != null) {
323                 ranges.setSize(size);
324                 // TODO what now?!
325                 httpConnection.getResponseHeaders().putOnly("Content-Range", ranges.format());
326             } else {
327                 httpConnection.getResponseHeaders().putOnly("Content-Length", Long.toString(size));
328             }
329         } else {
330             httpConnection.setResponseStatus(Status.NOT_FOUND);
331         }
332     }
333 
334     /**
335      * Implements PUT method
336      *
337      * @param httpConnection connection
338      */
339     public void methodPUT(HTTPConnection httpConnection) {
340         if (readOnly) {
341             httpConnection.setResponseStatus(Status.FORBIDDEN);
342             return;
343         }
344 
345         Object resource = findResource(httpConnection);
346         Object parentResource = adapter.findParentResource(resource);
347 
348         boolean oldFile = adapter.exists(resource);
349         if (oldFile && adapter.isCollection(resource)) {
350             httpConnection.setResponseStatus(Status.CONFLICT);
351             return;
352         } else if ((parentResource != null) && !adapter.exists(parentResource)) {
353             httpConnection.setResponseStatus(Status.CONFLICT);
354             return;
355         }
356 
357         boolean resourceLocked = false;
358         boolean parentLocked = false;
359         IF lockDetails = null;
360         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
361         if (lockingMechanism != null) {
362             lockDetails = new IF();
363             resourceLocked = lockingMechanism.isLocked(resource);
364             if (parentResource != null) {
365                 parentLocked = lockingMechanism.isLocked(parentResource);
366             }
367 
368             boolean ok;
369             if (parentLocked) {
370                 ok = lockDetails.parse(httpConnection, adapter, resource, parentResource);
371             } else {
372                 ok = lockDetails.parse(httpConnection, adapter, resource, null);
373             }
374             if (!ok) {
375                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
376                 return;
377             }
378 
379 //            if (!lockDetails.parse(httpConnection, adapter, resource, parentResource)) {
380 //                httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
381 //                return;
382 //            }
383 
384             if ((lockDetails.token == null) && resourceLocked) {
385                 // TODO
386                 // This is strange: litmus test expect to allow PUT on locked resource
387                 // where only parent's tagged if is supplied with appropriate lock token
388                 // To satisfy it we assumed that if parent token is supplied and is valid
389                 // on the resource then we will use it.
390                 if ((lockDetails.parentToken == null) || !lockingMechanism.isAccessAllowed(resource, lockDetails.parentToken)) {
391                     httpConnection.setResponseStatus(STATUS_LOCKED);
392                     return;
393                 }
394             } else if ((lockDetails.token != null) && !resourceLocked) {
395                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
396                 return;
397             }
398         }
399 
400         String contentRangeHeader = httpConnection.getRequestHeaders().getOnly("Content-Range");
401         Ranges ranges = null;
402         if (contentRangeHeader != null) {
403             ranges = Ranges.parseContentRange(contentRangeHeader);
404         }
405 
406         OutputStream out = null;
407         if (ranges == null) {
408             try {
409                 out = adapter.getOutputStream(resource);
410             } catch (IOException e) {
411                 httpConnection.setResponseStatus(Status.FORBIDDEN);
412                 return;
413             }
414             if (!resourceLocked && parentLocked) {
415                 Lock lock = lockingMechanism.findLock(lockDetails.parentToken);
416                 lockingMechanism.lockResource(lock, resource);
417             }
418         } else if (!ranges.isMultiRange()) {
419             if (ranges.getSize() >= 0) {
420 
421                 long from = ranges.getSingleRange().getFrom();
422                 long to = ranges.getSingleRange().getTo();
423                 long size = ranges.getSize();
424                 if ((from < 0) || (to >= size)) {
425                     httpConnection.setResponseStatus(Status.RANGE_NOT_SATISFIABLE);
426                 } else {
427                     try {
428                         out = adapter.getOutputStream(resource, from, to - from);
429                     } catch (IOException e) {
430                         httpConnection.setResponseStatus(Status.FORBIDDEN);
431                         return;
432                     }
433                     if (!resourceLocked && parentLocked) {
434                         Lock lock = lockingMechanism.findLock(lockDetails.parentToken);
435                         lockingMechanism.lockResource(lock, resource);
436                     }
437                 }
438             } else {
439                 httpConnection.setResponseStatus(Status.NOT_IMPLEMENTED);
440                 return;
441             }
442         } else {
443             // TODO not sure how this can happen!!!
444             httpConnection.setResponseStatus(Status.NOT_IMPLEMENTED);
445             return;
446         }
447 
448         if (out != null) {
449             try {
450                 boolean oldLoggingState = false;
451                 LoggingConnection loggingConnection = (LoggingConnection)httpConnection.adapt(LoggingConnection.class);
452                 if (loggingConnection != null) {
453                     oldLoggingState = loggingConnection.isLogging();
454                     loggingConnection.setLogging(false);
455                 }
456                 try {
457                     InputStream in = (InputStream)httpConnection.adapt(InputStream.class);
458 
459                     int bufSize = bufferSize;
460                     // if (bufSize > size) {
461                     //    bufSize = (int)size;
462                     // }
463                     byte[] buffer = new byte[bufSize];
464                     int r = 0;
465                     while (r >= 0) {
466                         r = bufSize;
467 
468                         r = in.read(buffer, 0, r);
469                         if (r > 0) {
470                             out.write(buffer, 0, r);
471                         }
472                     }
473                 } finally {
474                     if (loggingConnection != null) {
475                         loggingConnection.setLogging(oldLoggingState);
476                     }
477                     out.close();
478                 }
479             } catch (IOException e) {
480                 throw new RuntimeIOException(e);
481             }
482             if (oldFile) {
483                 httpConnection.setResponseStatus(Status.NO_CONTENT);
484             } else {
485                 httpConnection.setResponseStatus(Status.CREATED);
486             }
487         }
488     }
489 
490     /**
491      *
492      * <p>Following is hard to be maintained and is not followed stictly in this
493      * implementation:
494      * <p>
495      * <p><i>&quot;
496      *   If any resource identified by a member URI cannot be deleted then all
497      *    of the member's ancestors MUST NOT be deleted, so as to maintain
498      *    namespace consistency.
499      * &quot;</i></p>
500      * <p>Delete will stop at the first problem but deleted resources
501      * will remain deleted.
502      * </p>
503      *
504      * @param httpConnection
505      */
506     public void methodDELETE(HTTPConnection httpConnection) {
507         if (readOnly) {
508             httpConnection.setResponseStatus(Status.FORBIDDEN);
509             return;
510         }
511 
512         Object resource = findResource(httpConnection);
513         Object parentResource = adapter.findParentResource(resource);
514 
515         int depth = Depth.collectDepth(httpConnection);
516         if ((depth != Depth.INFINITY) && (depth != Depth.NONE)) {
517             // depth must be infinity
518             httpConnection.setResponseStatus(Status.BAD_REQUEST);
519             return;
520         }
521 
522         if (!adapter.exists(resource)) {
523             httpConnection.setResponseStatus(Status.NOT_FOUND);
524             return;
525         }
526 
527         MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
528 
529         boolean resourceLocked = false;
530         boolean parentLocked = false;
531         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
532         if (lockingMechanism != null) {
533             IF lockDetails = new IF();
534             resourceLocked = lockingMechanism.isLocked(resource);
535             if (parentResource != null) {
536                 parentLocked = lockingMechanism.isLocked(parentResource);
537             }
538             boolean ok;
539             if (parentLocked) {
540                 ok = lockDetails.parse(httpConnection, adapter, resource, parentResource);
541             } else {
542                 ok = lockDetails.parse(httpConnection, adapter, resource, null);
543             }
544             if (!ok) {
545                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
546                 return;
547             }
548 
549             if ((lockDetails.token == null) && resourceLocked) {
550                 httpConnection.setResponseStatus(STATUS_LOCKED);
551                 return;
552             } else if ((lockDetails.token != null) && !resourceLocked) {
553                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
554                 return;
555             } else if ((lockDetails.token != null) && adapter.isCollection(resource)) {
556                 if (isLockedRecursive(lockingMechanism, lockDetails, multiStatus, httpConnection, resource, adapter.getResourceName(resource))) {
557                     multiStatus.render();
558                 }
559             }
560         }
561 
562         if (deleteRecursive(multiStatus, "/", resource)) {
563             httpConnection.setResponseStatus(Status.NO_CONTENT);
564         } else {
565             multiStatus.render();
566         }
567     }
568 
569     /**
570      * Deletes resouces recursively
571      *
572      * @param multiStatus multi status response
573      * @param path current path
574      * @param resource resource
575      * @return <code>true</code> if successful
576      */
577     protected boolean deleteRecursive(MultiStatus multiStatus, String path, Object resource) {
578         boolean ok = true;
579         if (adapter.isCollection(resource)) {
580             String p = IOUtils.addPaths(path, adapter.getResourceName(resource));
581             Object[] resources = adapter.collectionElements(resource);
582             for (Object r : resources) {
583                 ok = ok && deleteRecursive(multiStatus, p, r);
584             }
585         }
586         ok = ok && deleteLeafImpl(multiStatus, path, resource);
587         return ok;
588     }
589 
590     /**
591      * Deletes a leaf (non-collection resource)
592      *
593      * @param multiStatus multi status response
594      * @param path current path
595      * @param resource resource
596      * @return <code>true</code> if successful
597      */
598     protected boolean deleteLeafImpl(MultiStatus multiStatus, String path, Object resource) {
599         Response response = multiStatus.newResponse(path);
600         try {
601             adapter.delete(resource);
602             response.setStatus(Status.OK);
603             return true;
604         } catch (IOException e) {
605             response.setResponseDescription(e.getMessage());
606             response.setStatus(Status.FORBIDDEN);
607             return false;
608         }
609     }
610 
611 
612     /**
613      * Implements PROPFIND method
614      *
615      * @param httpConnection http connection
616      */
617     public void methodPROPFIND(HTTPConnection httpConnection) {
618         Object resource = findResource(httpConnection);
619         if (adapter.exists(resource)) {
620             int depth = Depth.collectDepth(httpConnection);
621             MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
622             PropFind propFind;
623             try {
624                 propFind = (PropFind)readRequestXML(httpConnection, PropFind.class);
625             } catch (SAXException e) {
626                 httpConnection.setResponseStatus(Status.BAD_REQUEST);
627                 return;
628             }
629 
630             if (propFind == null) {
631                 // TODO very messy...
632                 propFind = new PropFind(null, null);
633                 propFind.setAllprop(true);
634             }
635 
636             if (adapter.exists(resource)) {
637                 Response response = multiStatus.newResponse();
638                 obtainProps(response, resource, propFind);
639                 if ((depth != Depth.ZERO) && adapter.isCollection(resource)) {
640                     Object[] resources = adapter.collectionElements(resource);
641                     if (resources != null) {
642                         for (int i = 0; i < resources.length; i++) {
643                             if (depth == Depth.INFINITY) {
644                                 String path = adapter.getResourceName(resources[i]);
645                                 obtainPropsRecursive(multiStatus, resource, propFind, path);
646                             } else {
647                                 response = multiStatus.newResponse(adapter.getResourceName(resources[i]));
648                                 obtainProps(response, resources[i], propFind);
649                             }
650                         }
651                     }
652                 }
653             } else {
654                 multiStatus.addSimpleResponse(Status.NOT_FOUND);
655             }
656             fixUnknownPropsResponse(multiStatus);
657             multiStatus.render(true);
658         } else {
659             httpConnection.setResponseStatus(Status.NOT_FOUND);
660         }
661     }
662 
663     /**
664      * Obtain properties recursively
665      *
666      * @param multiStatus multi status response
667      * @param resource resource
668      * @param propFind propfind structure
669      * @param path current path
670      */
671     protected void obtainPropsRecursive(MultiStatus multiStatus, Object resource, PropFind propFind, String path) {
672         Response response = multiStatus.newResponse(path);
673         obtainProps(response, resource, propFind);
674         if (adapter.isCollection(resource)) {
675             Object[] resources = adapter.collectionElements(resource);
676             if (resources != null) {
677                 for (int i = 0; i < resources.length; i++) {
678                     String p = IOUtils.addPaths(path, adapter.getResourceName(resources[i]));
679                     obtainPropsRecursive(multiStatus, resource, propFind, p);
680                 }
681             }
682         }
683     }
684 
685     /**
686      * Obtain properties of a given resource
687      * @param response single response structure
688      * @param resource resource
689      * @param propFind propfind structure
690      */
691     protected void obtainProps(Response response, Object resource, PropFind propFind) {
692 
693         if (propFind.isPropname()) {
694             Propstat propStat = new Propstat(response);
695             propStat.setStatus(Status.OK);
696             response.getPropStats().add(propStat);
697             ResponseProperty[] properties = adapter.getDefaultResponseProperties(resource);
698             for (ResponseProperty property : properties) {
699                 propStat.getProp().getProperties().add(property);
700             }
701         } else {
702             Propstat positive = null;
703             Map<String, Propstat> responses = null;
704 
705             List<RequestProperty> properties = null;
706             if (propFind.isAllprop()) {
707                 properties = Arrays.asList(adapter.getDefaultRequestProperties(resource));
708             } else {
709                 RequestProp prop = propFind.getProp();
710                 properties = prop.getProperties();
711             }
712 
713 
714 
715             for (RequestProperty property : properties) {
716                 ResponseProperty responseProperty = property.processResponse(adapter, resource);
717                 Status status = responseProperty.getStatus();
718                 if (status == Status.OK) {
719                     if (positive == null) {
720                         positive = new Propstat(response);
721                         positive.setStatus(Status.OK);
722                     }
723                     positive.getProp().getProperties().add(responseProperty);
724                 } else {
725                     if (responses == null) {
726                         responses = new HashMap<String, Propstat>();
727                     }
728                     Propstat propstat = responses.get(status.getCode());
729                     if (propstat == null) {
730                         propstat = new Propstat(response);
731                         propstat.setStatus(responseProperty.getStatus());
732                         responses.put(propstat.getStatus().getCode(), propstat);
733                     }
734                     propstat.getProp().getProperties().add(responseProperty);
735                 }
736             }
737             if (responses != null) {
738                 if (positive != null) {
739                     response.getPropStats().add(positive);
740                 }
741                 response.getPropStats().addAll(responses.values());
742             } else {
743                 if (positive != null) {
744                     response.getPropStats().add(positive);
745                 }
746             }
747         }
748     }
749 
750 
751     /**
752      * Implements PROPPATH method
753      *
754      * @param httpConnection connection
755      */
756     public void methodPROPPATCH(HTTPConnection httpConnection) {
757         if (readOnly) {
758             httpConnection.setResponseStatus(Status.FORBIDDEN);
759             return;
760         }
761 
762         Object resource = findResource(httpConnection);
763         int depth = Depth.collectDepth(httpConnection);
764 
765         PropertyUpdate propertyUpdate;
766         try {
767             propertyUpdate = (PropertyUpdate)readRequestXML(httpConnection, PropertyUpdate.class);
768         } catch (SAXException e) {
769             httpConnection.setResponseStatus(Status.BAD_REQUEST);
770             return;
771         }
772         if (propertyUpdate == null) {
773             httpConnection.setResponseStatus(Status.BAD_REQUEST);
774             return;
775         }
776         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
777         if (lockingMechanism != null) {
778             IF lockDetails = new IF();
779             boolean resourceLocked = lockingMechanism.isLocked(resource);
780             if (!lockDetails.parse(httpConnection, adapter, resource, null)) {
781                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
782                 return;
783             }
784 
785             if ((lockDetails.token == null) && resourceLocked) {
786                 httpConnection.setResponseStatus(STATUS_LOCKED);
787                 return;
788             } else if ((lockDetails.token != null) && !resourceLocked) {
789                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
790                 return;
791             } else if ((lockDetails.token != null) && adapter.isCollection(resource)) {
792                 MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
793                 if (isLockedRecursive(lockingMechanism, lockDetails, multiStatus, httpConnection, resource, adapter.getResourceName(resource))) {
794                     multiStatus.render(true);
795                 }
796             }
797         }
798 
799         if (adapter.exists(resource)) {
800             MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
801             updateProps(multiStatus, resource, depth, propertyUpdate);
802             fixUnknownPropsResponse(multiStatus);
803             multiStatus.render();
804         } else {
805             httpConnection.setResponseStatus(Status.NOT_FOUND);
806         }
807     }
808 
809     /**
810      * Updates properties
811      *
812      * @param multiStatus multi status response
813      * @param resource resource
814      * @param depth depth structure
815      * @param propertyUpdate propertyupdate structure
816      */
817     protected void updateProps(MultiStatus multiStatus, Object resource, int depth, PropertyUpdate propertyUpdate) {
818         Response response = multiStatus.newResponse();
819         updateProps(response, resource, propertyUpdate);
820         if ((depth != Depth.ZERO) && adapter.isCollection(resource)) {
821             Object[] resources = adapter.collectionElements(resource);
822             if (resources != null) {
823                 for (int i = 0; i < resources.length; i++) {
824                     if (depth == Depth.INFINITY) {
825                         String path = adapter.getResourceName(resources[i]);
826                         updatePropsRecursive(multiStatus, resource, propertyUpdate, path);
827                     } else {
828                         response = multiStatus.newResponse(adapter.getResourceName(resources[i]));
829                         updateProps(response, resources[i], propertyUpdate);
830                     }
831                 }
832             }
833         }
834     }
835 
836     /**
837      * Updates properties recursively
838      *
839      * @param multiStatus multi status response
840      * @param resource resource
841      * @param propertyUpdate propertyupdate structure
842      * @param path current path
843      */
844     protected void updatePropsRecursive(MultiStatus multiStatus, Object resource, PropertyUpdate propertyUpdate, String path) {
845         Response response = multiStatus.newResponse(path);
846         // TODO check lock!
847         updateProps(response, resource, propertyUpdate);
848         if (adapter.isCollection(resource)) {
849             Object[] resources = adapter.collectionElements(resource);
850             if (resources != null) {
851                 for (int i = 0; i < resources.length; i++) {
852                     String p = IOUtils.addPaths(path, adapter.getResourceName(resources[i]));
853                     updatePropsRecursive(multiStatus, resource, propertyUpdate, p);
854                 }
855             }
856         }
857     }
858 
859     /**
860      * Updates properties of a given resource (leaf)
861      *
862      * @param response single response
863      * @param resource resource
864      * @param propertyUpdate propertyupdate structure
865      */
866     protected void updateProps(Response response, Object resource, PropertyUpdate propertyUpdate) {
867         Propstat positive = null;
868         Map<String, Propstat> responses = null;
869         for (RequestProp prop : propertyUpdate.getProperties()) {
870 
871             for (RequestProperty property : prop.getProperties()) {
872                 ResponseProperty responseProperty = null;
873                 if (prop.getType() == RequestProp.SET) {
874                     responseProperty = property.processSetProperty(adapter, resource);
875                 } else {
876                     responseProperty = property.processRemoveProperty(adapter, resource);
877                 }
878                 Status status = responseProperty.getStatus();
879                 if (status == Status.OK) {
880                     if (positive == null) {
881                         positive = new Propstat(response);
882                         positive.setStatus(Status.OK);
883                     }
884                     positive.getProp().getProperties().add(responseProperty);
885                 } else {
886                     if (responses == null) {
887                         responses = new HashMap<String, Propstat>();
888                     }
889                     Propstat propstat = responses.get(status.getCode());
890                     if (propstat == null) {
891                         propstat = new Propstat(response);
892                         propstat.setStatus(responseProperty.getStatus());
893                         responses.put(propstat.getStatus().getCode(), propstat);
894                     }
895                     propstat.getProp().getProperties().add(responseProperty);
896                 }
897             }
898         }
899         if (responses != null) {
900             if (positive != null) {
901                 response.getPropStats().add(positive);
902             }
903             response.getPropStats().addAll(responses.values());
904         } else {
905             if (positive != null) {
906                 response.getPropStats().add(positive);
907             }
908         }
909     }
910 
911     /**
912      * Patch to fix response without &quot;known&quotl properties
913      * @param multiStatus multi status response
914      */
915     protected void fixUnknownPropsResponse(MultiStatus multiStatus) {
916         if ((multiStatus.getResponses().size() == 1) && (multiStatus.getResponses().get(0).getPropStats().size() == 0)) {
917             Propstat propStat = new Propstat(multiStatus.getResponses().get(0));
918             propStat.setStatus(Status.NOT_IMPLEMENTED);
919             multiStatus.getResponses().get(0).getPropStats().add(propStat);
920         }
921     }
922 
923 
924     /**
925      * Implements MKCOL method (making collection).
926      *
927      * @param httpConnection connection
928      */
929     public void methodMKCOL(HTTPConnection httpConnection) {
930         if (readOnly) {
931             httpConnection.setResponseStatus(Status.FORBIDDEN);
932             return;
933         }
934 
935         Object resource = findResource(httpConnection);
936         Object parentResource = adapter.findParentResource(resource);
937         if ((parentResource != null) && !adapter.exists(parentResource)) {
938             httpConnection.setResponseStatus(Status.CONFLICT);
939             return;
940         }
941 
942         if (adapter.exists(resource)) {
943             httpConnection.setResponseStatus(Status.METHOD_NOT_ALLOWED);
944             return;
945         }
946 
947         boolean resourceLocked = false;
948         boolean parentLocked = false;
949         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
950         if (lockingMechanism != null) {
951             IF lockDetails = new IF();
952             resourceLocked = lockingMechanism.isLocked(resource);
953             if (parentResource != null) {
954                 parentLocked = lockingMechanism.isLocked(parentResource);
955             }
956 
957             boolean ok;
958             if (parentLocked) {
959                 ok = lockDetails.parse(httpConnection, adapter, resource, parentResource);
960             } else {
961                 ok = lockDetails.parse(httpConnection, adapter, resource, null);
962             }
963             if (!ok) {
964                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
965                 return;
966             }
967 
968             if ((lockDetails.token == null) && resourceLocked) {
969                 httpConnection.setResponseStatus(STATUS_LOCKED);
970                 return;
971             } else if ((lockDetails.token != null) && !resourceLocked) {
972                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
973                 return;
974             }
975         }
976 
977         InputStream in = (InputStream)httpConnection.adapt(InputStream.class);
978         try {
979             int r = in.read();
980             if (r != -1) {
981                 httpConnection.setResponseStatus(Status.UNSUPPORTED_MEDIA_TYPE);
982                 return;
983             }
984         } catch (IOException ignore) {
985         }
986 
987         try {
988             adapter.makeCollection(resource);
989             httpConnection.setResponseStatus(Status.CREATED);
990         } catch (IOException e) {
991             // TODO add log statement
992             httpConnection.setResponseStatus(Status.FORBIDDEN);
993         }
994     }
995 
996     /**
997      * Implements copy method
998      *
999      * @param httpConnection connection
1000      */
1001     public void methodCOPY(HTTPConnection httpConnection) {
1002         if (readOnly) {
1003             httpConnection.setResponseStatus(Status.FORBIDDEN);
1004             return;
1005         }
1006 
1007         int depth = Depth.collectDepth(httpConnection);
1008         if (depth == Depth.NONE) {
1009             depth = Depth.INFINITY;
1010         }
1011         /*PropertyBehavior propertyBehavior;*/try {
1012             /*propertyBehavior = (PropertyBehavior)*/readRequestXML(httpConnection, PropertyBehavior.class);
1013         } catch (SAXException e) {
1014             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1015             return;
1016         }
1017         // TODO use property behavior!!!
1018         if (depth == Depth.ONE) {
1019             // MOVE MUST have infinity as a depth
1020             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1021             return;
1022         }
1023 
1024         Object fromResource = findResource(httpConnection);
1025 
1026         boolean overwrite = !"F".equals(httpConnection.getRequestHeaders().getOnly("Overwrite"));
1027         String destHeader = httpConnection.getRequestHeaders().getOnly("Destination");
1028         Object destResource = URIUtils.uriToResource(httpConnection, adapter, destHeader);
1029         if (destResource == null) {
1030             httpConnection.setResponseStatus(Status.FORBIDDEN);
1031             return;
1032         }
1033 
1034         if (adapter.exists(destResource) && !overwrite) {
1035             httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1036             return;
1037         }
1038 
1039         Object destParentResource = adapter.findParentResource(destResource);
1040         if ((destParentResource != null) && !adapter.exists(destParentResource)) {
1041             httpConnection.setResponseStatus(Status.CONFLICT);
1042             return;
1043         }
1044 
1045         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
1046         boolean destLocked = false;
1047         boolean destParentLocked = false;
1048         IF lockDetails = null;
1049 
1050         if (lockingMechanism != null) {
1051             lockDetails = new IF();
1052             destLocked = lockingMechanism.isLocked(destResource);
1053             if (destParentResource != null) {
1054                 destParentLocked = lockingMechanism.isLocked(destParentResource);
1055             }
1056 
1057             boolean ok;
1058             if (destParentLocked) {
1059                 ok = lockDetails.parse(httpConnection, adapter, destResource, destParentResource);
1060             } else {
1061                 ok = lockDetails.parse(httpConnection, adapter, destResource, null);
1062             }
1063             if (!ok) {
1064                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1065                 return;
1066             }
1067 
1068             if ((lockDetails.token == null) && destLocked) {
1069                 httpConnection.setResponseStatus(STATUS_LOCKED);
1070                 return;
1071             } else if ((lockDetails.token != null) && !destLocked) {
1072                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1073                 return;
1074             } else if ((lockDetails.token != null) && adapter.isCollection(destResource)) {
1075                 // TODO Deep check if locked?
1076             }
1077         }
1078 
1079         boolean newFile = true;
1080         if (overwrite && adapter.exists(destResource)) {
1081             newFile = false;
1082             MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
1083             if (!deleteRecursive(multiStatus, "/", destResource)) {
1084                 multiStatus.render();
1085                 return;
1086             }
1087         }
1088 
1089         try {
1090             adapter.copy(fromResource, destResource, (depth == Depth.INFINITY));
1091             if (newFile) {
1092                 if (destParentLocked) {
1093                     Lock lock = lockingMechanism.findLock(lockDetails.token);
1094                     lockingMechanism.lockResource(lock, destResource);
1095                 }
1096                 httpConnection.setResponseStatus(Status.CREATED);
1097             } else {
1098                 httpConnection.setResponseStatus(Status.NO_CONTENT);
1099             }
1100         } catch (IOException e) {
1101             httpConnection.setResponseStatus(Status.FORBIDDEN);
1102         }
1103 
1104     }
1105 
1106     /**
1107      * Moves resource
1108      *
1109      * <p><i>Note: property behaviour is ignored in this implementation!</i></p>
1110      * @param httpConnection connection
1111      */
1112     public void methodMOVE(HTTPConnection httpConnection) {
1113         if (readOnly) {
1114             httpConnection.setResponseStatus(Status.FORBIDDEN);
1115             return;
1116         }
1117 
1118         int depth = Depth.collectDepth(httpConnection);
1119         if (depth == Depth.NONE) {
1120             depth = Depth.INFINITY;
1121         }
1122         /*PropertyBehavior propertyBehavior = (PropertyBehavior)*/try {
1123             readRequestXML(httpConnection, PropertyBehavior.class);
1124         } catch (SAXException e) {
1125             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1126             return;
1127         }
1128 
1129         if (depth != Depth.INFINITY) {
1130             // MOVE MUST have infinity as a depth
1131             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1132             return;
1133         }
1134 
1135         Object fromResource = findResource(httpConnection);
1136 
1137         boolean overwrite = !"F".equals(httpConnection.getRequestHeaders().getOnly("Overwrite"));
1138         String destHeader = httpConnection.getRequestHeaders().getOnly("Destination");
1139         Object destResource = URIUtils.uriToResource(httpConnection, adapter, destHeader);
1140 
1141         if (destResource == null) {
1142             httpConnection.setResponseStatus(Status.FORBIDDEN);
1143             return;
1144         }
1145 
1146         if (adapter.exists(destResource) && !overwrite) {
1147             httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1148             return;
1149         }
1150 
1151         Object destParentResource = adapter.findParentResource(destResource);
1152         if ((destParentResource != null) && !adapter.exists(destParentResource)) {
1153             httpConnection.setResponseStatus(Status.CONFLICT);
1154             return;
1155         }
1156 
1157         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
1158         boolean destLocked = false;
1159         boolean destParentLocked = false;
1160         IF lockDetails = null;
1161 
1162         if (lockingMechanism != null) {
1163             lockDetails = new IF();
1164             Object fromParentResource = adapter.findParentResource(fromResource);
1165             boolean fromLocked = lockingMechanism.isLocked(fromResource);
1166             boolean fromParentLocked = false;
1167             if (fromParentResource != null) {
1168                 fromParentLocked = lockingMechanism.isLocked(fromParentResource);
1169             }
1170 
1171             boolean ok;
1172             if (fromParentLocked) {
1173                 ok = lockDetails.parse(httpConnection, adapter, fromResource, fromParentResource);
1174             } else {
1175                 ok = lockDetails.parse(httpConnection, adapter, fromResource, null);
1176             }
1177             if (!ok) {
1178                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1179                 return;
1180             }
1181 
1182             if ((lockDetails.token == null) && fromLocked) {
1183                 httpConnection.setResponseStatus(STATUS_LOCKED);
1184                 return;
1185             } else if ((lockDetails.token != null) && !fromLocked) {
1186                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1187                 return;
1188             } else if ((lockDetails.token != null) && adapter.isCollection(fromResource)) {
1189                 MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
1190                 if (isLockedRecursive(lockingMechanism, lockDetails, multiStatus, httpConnection, fromResource, adapter.getResourceName(fromResource))) {
1191                     multiStatus.render();
1192                     return;
1193                 }
1194             }
1195 
1196 
1197             destLocked = lockingMechanism.isLocked(destResource);
1198             if (destLocked) {
1199                 if ((lockDetails.clearedResources == null) || !lockDetails.clearedResources.contains(destResource)) {
1200                     if (!lockingMechanism.isAccessAllowed(destResource, lockDetails.token)) {
1201                         httpConnection.setResponseStatus(STATUS_LOCKED);
1202                         return;
1203                     }
1204                 }
1205             }
1206 
1207             if (destParentResource != null) {
1208                 destParentLocked = lockingMechanism.isLocked(destParentResource);
1209             }
1210             if (destParentLocked) {
1211                 if ((lockDetails.clearedResources == null) || !lockDetails.clearedResources.contains(destParentResource)) {
1212                     if (!lockingMechanism.isAccessAllowed(destParentResource, lockDetails.token)) {
1213                         httpConnection.setResponseStatus(STATUS_LOCKED);
1214                         return;
1215                     }
1216                 }
1217             }
1218         }
1219 
1220         boolean newFile = true;
1221         if (overwrite && !fromResource.equals(destResource) && adapter.exists(destResource)) {
1222             newFile = false;
1223             MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
1224             if (!deleteRecursive(multiStatus, "/", destResource)) {
1225                 multiStatus.render();
1226                 return;
1227             }
1228         }
1229 
1230         try {
1231             adapter.move(fromResource, destResource);
1232             if (newFile) {
1233                 if (destParentLocked) {
1234                     Lock lock = lockingMechanism.findLock(lockDetails.token);
1235                     lockingMechanism.lockResource(lock, destResource);
1236                 }
1237                 httpConnection.setResponseStatus(Status.CREATED);
1238             } else {
1239                 httpConnection.setResponseStatus(Status.NO_CONTENT);
1240             }
1241             if ((lockingMechanism != null) && lockingMechanism.isLocked(fromResource)) {
1242                 lockingMechanism.removeLocks(fromResource);
1243             }
1244         } catch (IOException e) {
1245             httpConnection.setResponseStatus(Status.FORBIDDEN);
1246         }
1247     }
1248 
1249     /**
1250      * Implements LOCK method. This method relies that adapter has non-null lcoking mechanism
1251      * defined.
1252      *
1253      * @param httpConnection connection
1254      */
1255     public void methodLOCK(HTTPConnection httpConnection) {
1256         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
1257         Object resource = findResource(httpConnection);
1258 
1259         LockInfo lockInfo;
1260         try {
1261             lockInfo = (LockInfo)readRequestXML(httpConnection, LockInfo.class);
1262         } catch (SAXException e) {
1263             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1264             return;
1265         }
1266         if (lockingMechanism == null) {
1267             httpConnection.setResponseStatus(Status.METHOD_NOT_ALLOWED);
1268             return;
1269         }
1270 
1271         int depth = Depth.collectDepth(httpConnection);
1272         if (depth == Depth.NONE) {
1273             depth = Depth.INFINITY;
1274         }
1275         if ((depth == Depth.ONE)/* || (lockInfo == null)*/) {
1276             httpConnection.setResponseStatus(Status.BAD_REQUEST);
1277         } else {
1278             Lock[] locks = null;
1279             IF lockDetails = new IF();
1280 
1281             if (!lockDetails.parse(httpConnection, adapter, resource, null)) {
1282                 httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1283                 return;
1284             }
1285 
1286             if (lockingMechanism.isLocked(resource)) {
1287                 if (lockInfo != null) {
1288                     locks = lockingMechanism.getLocks(resource);
1289                     for (Lock lock : locks) {
1290                         if (lock.getScope() == LockingMechanism.SCOPE_EXCLUSIVE) {
1291                             httpConnection.setResponseStatus(STATUS_LOCKED);
1292                             return;
1293                         }
1294                     }
1295                 }
1296             } else {
1297                 if (lockInfo == null) {
1298                     httpConnection.setResponseStatus(Status.PRECONDITION_FAILED);
1299                     return;
1300                 }
1301             }
1302 
1303             if (adapter.exists(resource)) {
1304                 Timeout[] timeouts = collectTimeouts(httpConnection);
1305                 MultiStatus multiStatus = new MultiStatus(adapter.getNamespacesProvider(), httpConnection);
1306 
1307                 Lock lock = null;
1308                 if (lockInfo != null) {
1309 //                    if (lockInfo.getScope() == LockingMechanism.SCOPE_EXCLUSIVE) {
1310 //                        if (locks != null) {
1311 //                            httpConnection.setResponseStatus(STATUS_LOCKED);
1312 //                            return;
1313 //                        }
1314 //                    }
1315 
1316                     Object owner = lockInfo.getOwner();
1317                     if (timeouts == null) {
1318                         lock = lockingMechanism.createLock(lockInfo.getType(), lockInfo.getScope(), owner, null, depth);
1319                     } else if (timeouts.length == 1) {
1320                         lock = lockingMechanism.createLock(lockInfo.getType(), lockInfo.getScope(), owner, timeouts[0], depth);
1321                     } else {
1322                         int i = 0;
1323                         while ((lock == null) && (i < timeouts.length)) {
1324                             lock = lockingMechanism.createLock(lockInfo.getType(), lockInfo.getScope(), owner, timeouts[i], depth);
1325                             i++;
1326                         }
1327                     }
1328                     Response response = multiStatus.newResponse();
1329                     boolean ok = true;
1330                     if (lockImpl(response, lock, lockingMechanism, lockInfo, owner, depth == Depth.INFINITY, resource)) {
1331                         if ((depth != Depth.ZERO) && adapter.isCollection(resource)) {
1332                             Object[] resources = adapter.collectionElements(resource);
1333                             if (resources != null) {
1334                                 int i = 0;
1335                                 while (ok && (i < resources.length)) {
1336                                     String path = adapter.getResourceName(resources[i]);
1337                                     ok = lockRecursive(multiStatus, lock, lockingMechanism, lockInfo, owner, resources[i], path);
1338                                     i ++;
1339                                 }
1340                             }
1341                         }
1342                     } else {
1343                         ok = false;
1344                     }
1345                     if (ok) {
1346                         httpConnection.getResponseHeaders().putOnly("Lock-Token", lock.getToken().toString());
1347                     } else {
1348                         lockingMechanism.unlockResources(lock);
1349                     }
1350                     multiStatus.render(false);
1351                 } else {
1352                     // Refresh
1353                     if (lockDetails.token != null) {
1354                         lock = lockingMechanism.findLock(lockDetails.token);
1355                         if ((timeouts == null) || (timeouts.length == 0)) {
1356                             lock.refreshTimeout(null);
1357                         } else {
1358                             boolean ok = false;
1359                             int i = 0;
1360                             while (!ok) {
1361                                 ok = lock.refreshTimeout(timeouts[i]);
1362                                 i++;
1363                             }
1364                         }
1365                         httpConnection.getResponseHeaders().putOnly("Timeout", lock.getTimeout().asString());
1366                         Response response = multiStatus.newResponse();
1367                         Propstat propstat = new Propstat(response);
1368                         propstat.setStatus(Status.OK);
1369                         LockDiscovery lockDiscovery = new LockDiscovery(Status.OK);
1370                         lockDiscovery.getLocks().add(lock);
1371                         propstat.getProp().getProperties().add(lockDiscovery);
1372                         response.getPropStats().add(propstat);
1373                         multiStatus.render(false);
1374                     } else {
1375                         httpConnection.setResponseStatus(Status.CONFLICT);
1376                     }
1377                 }
1378             } else {
1379                 httpConnection.setResponseStatus(Status.NOT_FOUND);
1380             }
1381         }
1382     }
1383 
1384     /**
1385      * Locks resource recursively.
1386      *
1387      * @param multiStatus multi status response
1388      * @param lock lock
1389      * @param lockingMechanism locking mechanism
1390      * @param lockInfo lock info structure
1391      * @param owner owner
1392      * @param resource resource
1393      * @param path path
1394      * @return <code>true</code> if locking succeeded
1395      */
1396     protected boolean lockRecursive(MultiStatus multiStatus, Lock lock, LockingMechanism lockingMechanism, LockInfo lockInfo, Object owner, Object resource, String path) {
1397         Response response = multiStatus.newResponse(path);
1398         if (lockImpl(response, lock, lockingMechanism, lockInfo, owner, true, resource)) {
1399             if (adapter.isCollection(resource)) {
1400                 Object[] resources = adapter.collectionElements(resource);
1401                 if (resources != null) {
1402                     for (int i = 0; i < resources.length; i++) {
1403                         String p = IOUtils.addPaths(path, adapter.getResourceName(resources[i]));
1404                         if (!lockRecursive(multiStatus, lock, lockingMechanism, lockInfo, owner, resource, p)) {
1405                             return false;
1406                         }
1407                     }
1408                 }
1409             }
1410             return true;
1411         } else {
1412             return false;
1413         }
1414     }
1415 
1416     /**
1417      * Locks the resource
1418      *
1419      * @param response single response
1420      * @param lock lock
1421      * @param lockingMechanism locking mechanism
1422      * @param lockInfo lockinfo structure
1423      * @param owner owner
1424      * @param recursive is recursive
1425      * @param resource resource
1426      * @return <code>true</code> if succeeded
1427      */
1428     protected boolean lockImpl(Response response, Lock lock, LockingMechanism lockingMechanism, LockInfo lockInfo, Object owner, boolean recursive, Object resource) {
1429         // TODO Check this!!!
1430         Lock[] locks = lockingMechanism.getLocks(resource);
1431         if (locks != null) {
1432             if ((lockInfo.getScope() == LockingMechanism.SCOPE_EXCLUSIVE)) {
1433                 response.setStatus(STATUS_LOCKED);
1434                 return false;
1435             } else {
1436                 for (Lock l : locks) {
1437                     if (l.getScope() == LockingMechanism.SCOPE_EXCLUSIVE) {
1438                         response.setStatus(STATUS_LOCKED);
1439                         return false;
1440                     }
1441                 }
1442             }
1443         }
1444 
1445 //        if (!lockingMechanism.isAccessAllowed(resource, lock.getToken())) {
1446 //            response.setStatus(STATUS_LOCKED);
1447 //            return false;
1448 //        } else
1449         int[] lockScopes = lockingMechanism.getSupportedLockScopes(resource);
1450         if (lockScopes.length == 0) {
1451             response.setStatus(Status.PRECONDITION_FAILED);
1452             return false;
1453         } else {
1454             if (lockingMechanism.lockResource(lock, resource)) {
1455                 Propstat propstat = new Propstat(response);
1456                 LockDiscovery lockDiscovery = new LockDiscovery(Status.OK);
1457                 lockDiscovery.getLocks().add(lock);
1458                 propstat.getProp().getProperties().add(lockDiscovery);
1459                 response.getPropStats().add(propstat);
1460                 propstat.setStatus(Status.OK);
1461                 return true;
1462             } else {
1463                 response.setStatus(Status.PRECONDITION_FAILED);
1464                 return false;
1465             }
1466         }
1467     }
1468 
1469     /**
1470      * Implements UNLOCK method. Ir relies on adapter providing locking mechanism.
1471      *
1472      * @param httpConnection connection
1473      */
1474     public void methodUNLOCK(HTTPConnection httpConnection) {
1475         Object resource = findResource(httpConnection);
1476         LockingMechanism lockingMechanism = adapter.getLockingMechanism();
1477         if (lockingMechanism == null) {
1478             httpConnection.setResponseStatus(Status.NO_CONTENT);
1479             return;
1480         }
1481         String token = httpConnection.getRequestHeaders().getOnly("Lock-Token");
1482         if (token != null) {
1483             if (token.startsWith("<") && token.endsWith(">") && (token.length() > 2)) {
1484                 token = token.substring(1, token.length() - 1);
1485             } else {
1486                 httpConnection.setResponseStatus(Status.BAD_REQUEST);
1487                 return;
1488             }
1489         }
1490 
1491         if (lockingMechanism.isAccessAllowed(resource, token)) {
1492             httpConnection.setResponseStatus(STATUS_LOCKED);
1493         }
1494 
1495         if (adapter.exists(resource)) {
1496             Lock lock = lockingMechanism.findLock(token);
1497             if (lock != null) {
1498                 lockingMechanism.unlockResources(lock);
1499                 httpConnection.setResponseStatus(Status.NO_CONTENT);
1500             } else {
1501                 httpConnection.setResponseStatus(STATUS_LOCKED);
1502             }
1503         } else {
1504             httpConnection.setResponseStatus(Status.NOT_FOUND);
1505         }
1506     }
1507 
1508     /**
1509      * Checks recursively if resource is locked and all of it's children.
1510      * @param lockingMechanism locking mechanism
1511      * @param lockDetails lock details
1512      * @param multiStatus multi status response
1513      * @param httpConnection connection
1514      * @param resource resource
1515      * @param path current path
1516      * @return <code>true</code> if resource is locked
1517      */
1518     protected boolean isLockedRecursive(LockingMechanism lockingMechanism, IF lockDetails, MultiStatus multiStatus, HTTPConnection httpConnection, Object resource, String path) {
1519         boolean locked = false;
1520         Object[] resources = adapter.collectionElements(resource);
1521         if ((resources != null) && (resources.length > 0)) {
1522             for (Object r : resources) {
1523                 String p = IOUtils.addPaths(path, adapter.getResourceName(r));
1524                 if ((lockDetails.clearedResources == null) || !lockDetails.clearedResources.contains(r)) {
1525                     if (!lockingMechanism.isAccessAllowed(r, lockDetails.token)) {
1526                         Response response = multiStatus.newResponse(p);
1527                         response.setStatus(STATUS_FAILED_DEPENDENCY);
1528                         locked = true;
1529                     }
1530                 }
1531                 if (adapter.isCollection(r)) {
1532                     isLockedRecursive(lockingMechanism, lockDetails, multiStatus, httpConnection, r, p);
1533                 }
1534             }
1535         }
1536 
1537         return locked;
1538     }
1539 
1540     /**
1541      * Collects range from the &quot;Range&quot; header.
1542      *
1543      * @param connection http connection
1544      * @return {@link Ranges} or <code>null</code>
1545      */
1546     protected Ranges collectRange(HTTPConnection connection) {
1547         String rangesHeader = connection.getRequestHeaders().getOnly("Range");
1548         if (rangesHeader != null) {
1549             Ranges ranges = Ranges.parseRange(rangesHeader);
1550             return ranges;
1551         } else {
1552             return null;
1553         }
1554     }
1555 
1556     /**
1557      * Collects timeouts from the &quot;Timeout&quot; header
1558      * @param connection http connection
1559      * @return {@link Timeout} structure or <code>null</code>
1560      */
1561     protected Timeout[] collectTimeouts(HTTPConnection connection) {
1562         String timeoutHeader = connection.getRequestHeaders().getOnly("Timeout");
1563         if (timeoutHeader != null) {
1564             int i = timeoutHeader.indexOf(',');
1565             if (i >= 0) {
1566                 ArrayList<Timeout> timeouts = new ArrayList<Timeout>();
1567                 StringTokenizer tokenizer = new StringTokenizer(timeoutHeader, ",");
1568                 while (tokenizer.hasMoreTokens()) {
1569                     Timeout timeout = Timeout.parse(tokenizer.nextToken());
1570                     timeouts.add(timeout);
1571                 }
1572                 Timeout[] res = new Timeout[timeouts.size()];
1573                 return timeouts.toArray(res);
1574             } else {
1575                 Timeout timeout = Timeout.parse(timeoutHeader);
1576                 return new Timeout[]{timeout};
1577             }
1578         } else {
1579             return null;
1580         }
1581     }
1582 
1583 
1584     /**
1585      * Reads request XML strcture and parses it returning an object based on {@link WebDAVXMLHandler}.
1586      *
1587      * @param adaptable adaptable object to be used for obtaining input stream
1588      * @param expectedClass expected class
1589      * @return an object of an expected class or <code>null</code>
1590      * @throws SAXException if there was an expcetion while parsing
1591      */
1592     protected Object readRequestXML(Adaptable adaptable, Class<?> expectedClass) throws SAXException {
1593         InputStream inputStream = (InputStream)adaptable.adapt(InputStream.class);
1594         if ((inputStream instanceof HTTPBufferedInputStream)
1595                 && (((HTTPBufferedInputStream)inputStream).getContentLength() == 0)) {
1596             return null;
1597         } else {
1598             try {
1599                 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
1600                 SAXParser parser = parserFactory.newSAXParser();
1601                 XMLReader reader = parser.getXMLReader();
1602                 reader.setFeature("http://xml.org/sax/features/namespaces", true);
1603 
1604                 InputSource inputSource = new InputSource(inputStream);
1605                 WebDAVXMLHandler webDAVXMLHandler = new WebDAVXMLHandler(adapter.getNamespacesProvider());
1606                 parser.parse(inputSource, webDAVXMLHandler);
1607                 Object result = webDAVXMLHandler.getResultObject();
1608                 if ((result != null) && (expectedClass.isAssignableFrom(result.getClass()))) {
1609                     return result;
1610                 }
1611             } catch (ParserConfigurationException e) {
1612                 e.printStackTrace();
1613             } catch (IOException e) {
1614                 e.printStackTrace();
1615             }
1616         }
1617         return null;
1618     }
1619 
1620 
1621 }