View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.webdav4;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.net.HttpURLConnection;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.vfs2.FileContentInfoFactory;
29  import org.apache.commons.vfs2.FileNotFolderException;
30  import org.apache.commons.vfs2.FileNotFoundException;
31  import org.apache.commons.vfs2.FileObject;
32  import org.apache.commons.vfs2.FileSystemException;
33  import org.apache.commons.vfs2.FileType;
34  import org.apache.commons.vfs2.NameScope;
35  import org.apache.commons.vfs2.provider.AbstractFileName;
36  import org.apache.commons.vfs2.provider.DefaultFileContent;
37  import org.apache.commons.vfs2.provider.GenericURLFileName;
38  import org.apache.commons.vfs2.provider.http4.Http4FileObject;
39  import org.apache.commons.vfs2.util.FileObjectUtils;
40  import org.apache.commons.vfs2.util.MonitorOutputStream;
41  import org.apache.http.HttpEntity;
42  import org.apache.http.HttpResponse;
43  import org.apache.http.HttpStatus;
44  import org.apache.http.client.methods.CloseableHttpResponse;
45  import org.apache.http.client.methods.HttpPut;
46  import org.apache.http.client.methods.HttpUriRequest;
47  import org.apache.http.client.utils.DateUtils;
48  import org.apache.http.entity.ByteArrayEntity;
49  import org.apache.http.entity.ContentType;
50  import org.apache.http.util.EntityUtils;
51  import org.apache.jackrabbit.webdav.DavConstants;
52  import org.apache.jackrabbit.webdav.DavException;
53  import org.apache.jackrabbit.webdav.MultiStatus;
54  import org.apache.jackrabbit.webdav.MultiStatusResponse;
55  import org.apache.jackrabbit.webdav.client.methods.BaseDavRequest;
56  import org.apache.jackrabbit.webdav.client.methods.HttpCheckin;
57  import org.apache.jackrabbit.webdav.client.methods.HttpCheckout;
58  import org.apache.jackrabbit.webdav.client.methods.HttpDelete;
59  import org.apache.jackrabbit.webdav.client.methods.HttpMkcol;
60  import org.apache.jackrabbit.webdav.client.methods.HttpMove;
61  import org.apache.jackrabbit.webdav.client.methods.HttpPropfind;
62  import org.apache.jackrabbit.webdav.client.methods.HttpProppatch;
63  import org.apache.jackrabbit.webdav.client.methods.HttpVersionControl;
64  import org.apache.jackrabbit.webdav.property.DavProperty;
65  import org.apache.jackrabbit.webdav.property.DavPropertyName;
66  import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
67  import org.apache.jackrabbit.webdav.property.DavPropertySet;
68  import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
69  import org.apache.jackrabbit.webdav.version.DeltaVConstants;
70  import org.apache.jackrabbit.webdav.version.VersionControlledResource;
71  import org.apache.jackrabbit.webdav.xml.Namespace;
72  import org.w3c.dom.Node;
73  
74  /**
75   * A WebDAV file.
76   *
77   * @since 2.5.0
78   */
79  public class Webdav4FileObject extends Http4FileObject<Webdav4FileSystem> {
80  
81      /**
82       * An OutputStream that writes to a WebDAV resource.
83       * <p>
84       * TODO - Use piped stream to avoid temporary file.
85       * </p>
86       */
87      private class WebdavOutputStream extends MonitorOutputStream {
88          private final Webdav4FileObject file;
89  
90          WebdavOutputStream(final Webdav4FileObject file) {
91              super(new ByteArrayOutputStream());
92              this.file = file;
93          }
94  
95          private boolean createVersion(final String urlStr) {
96              try {
97                  final HttpVersionControl request = setupRequest(new HttpVersionControl(urlStr));
98                  // AutoClose the underlying HTTP connection which is held by the response object
99                  try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
100                     return true;
101                 }
102             } catch (final Exception ex) {
103                 return false;
104             }
105         }
106 
107         /**
108          * Called after this stream is closed.
109          */
110         @Override
111         protected void onClose() throws IOException {
112             final HttpEntity entity = new ByteArrayEntity(((ByteArrayOutputStream) out).toByteArray());
113             final GenericURLFileName fileName = (GenericURLFileName) getName();
114             final String urlStr = toUrlString(fileName);
115             if (builder.isVersioning(getFileSystem().getFileSystemOptions())) {
116                 DavPropertySet set = null;
117                 boolean fileExists = true;
118                 boolean isCheckedIn = true;
119                 try {
120                     set = getPropertyNames(fileName);
121                 } catch (final FileNotFoundException fnfe) {
122                     fileExists = false;
123                 }
124                 if (fileExists && set != null) {
125                     if (set.contains(VersionControlledResource.CHECKED_OUT)) {
126                         isCheckedIn = false;
127                     } else if (!set.contains(VersionControlledResource.CHECKED_IN)) {
128                         DavProperty<?> prop = set.get(VersionControlledResource.AUTO_VERSION);
129                         if (prop != null) {
130                             prop = getProperty(fileName, VersionControlledResource.AUTO_VERSION);
131                             if (DeltaVConstants.XML_CHECKOUT_CHECKIN.equals(prop.getValue())) {
132                                 createVersion(urlStr);
133                             }
134                         }
135                     }
136                 }
137                 if (fileExists && isCheckedIn) {
138                     try {
139                         final HttpCheckout request = setupRequest(new HttpCheckout(urlStr));
140                         // AutoClose the underlying HTTP connection which is held by the response object
141                         try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
142                             isCheckedIn = false;
143                         }
144                     } catch (final FileSystemException ex) {
145                         log(ex);
146                     }
147                 }
148 
149                 try {
150                     final HttpPut request = new HttpPut(urlStr);
151                     request.setEntity(entity);
152                     setupRequest(request);
153                     // AutoClose the underlying HTTP connection which is held by the response object
154                     try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
155                         setUserName(fileName, urlStr);
156                     }
157                 } catch (final FileSystemException ex) {
158                     if (!isCheckedIn) {
159                         try {
160                             final HttpCheckin request = new HttpCheckin(urlStr);
161                             setupRequest(request);
162                             // AutoClose the underlying HTTP connection which is held by the response object
163                             try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
164                                 isCheckedIn = true;
165                             }
166                         } catch (final Exception e) {
167                             // Going to throw original.
168                             log(e);
169                         }
170                         throw ex;
171                     }
172                 }
173                 if (!fileExists) {
174                     createVersion(urlStr);
175                     try {
176                         final DavPropertySet props = getPropertyNames(fileName);
177                         isCheckedIn = !props.contains(VersionControlledResource.CHECKED_OUT);
178                     } catch (final FileNotFoundException fnfe) {
179                         log(fnfe);
180                     }
181                 }
182                 if (!isCheckedIn) {
183                     final HttpCheckin request = new HttpCheckin(urlStr);
184                     setupRequest(request);
185                     // AutoClose the underlying HTTP connection which is held by the response object
186                     try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
187                         isCheckedIn = true;
188                     }
189                 }
190             } else {
191                 final HttpPut request = new HttpPut(urlStr);
192                 request.setEntity(entity);
193                 setupRequest(request);
194                 // AutoClose the underlying HTTP connection which is held by the response object
195                 try (CloseableHttpResponse unused = (CloseableHttpResponse) executeRequest(request)) {
196                     try {
197                         setUserName(fileName, urlStr);
198                     } catch (final IOException e) {
199                         // Unable to set the user name.
200                         log(e);
201                     }
202                 }
203             }
204             ((DefaultFileContent) file.getContent()).resetAttributes();
205         }
206 
207         private void setUserName(final GenericURLFileName fileName, final String urlStr) throws IOException {
208             final DavPropertySet setProperties = new DavPropertySet();
209             final DavPropertyNameSet removeProperties = new DavPropertyNameSet();
210             String name = builder.getCreatorName(getFileSystem().getFileSystemOptions());
211             final String userName = fileName.getUserName();
212             if (name == null) {
213                 name = userName;
214             } else if (userName != null) {
215                 final String comment = "Modified by user " + userName;
216                 setProperties.add(new DefaultDavProperty<>(DeltaVConstants.COMMENT, comment));
217             }
218             setProperties.add(new DefaultDavProperty<>(DeltaVConstants.CREATOR_DISPLAYNAME, name));
219             final HttpProppatch request = setupRequest(new HttpProppatch(urlStr, setProperties, removeProperties));
220             // AutoClose the underlying HTTP connection which is held by the response object
221             try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
222                 // TODO: workaround due to PMD violation 'Empty try body - you could rename the resource to 'ignored'
223                 request.succeeded(res);
224             }
225         }
226     }
227 
228     /** The character set property name. */
229     public static final DavPropertyName RESPONSE_CHARSET = DavPropertyName.create("response-charset");
230 
231     /**
232      * An empty immutable {@code Webdav4FileObject} array.
233      */
234     private static final Webdav4FileObject[] EMPTY_ARRAY = {};
235 
236     /** The FileSystemConfigBuilder */
237     private final Webdav4FileSystemConfigBuilder builder;
238 
239     /**
240      * Constructs a new instance.
241      *
242      * @param name file name.
243      * @param fileSystem file system.
244      * @throws FileSystemException if any error occurs
245      */
246     protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem)
247             throws FileSystemException {
248         this(name, fileSystem, Webdav4FileSystemConfigBuilder.getInstance());
249     }
250 
251     /**
252      * Constructs a new instance.
253      *
254      * @param name file name.
255      * @param fileSystem file system.
256      * @param builder builds the file system configuration.
257      * @throws FileSystemException if any error occurs
258      */
259     protected Webdav4FileObject(final AbstractFileName name, final Webdav4FileSystem fileSystem,
260             final Webdav4FileSystemConfigBuilder builder) throws FileSystemException {
261         super(name, fileSystem, builder);
262         this.builder = builder;
263     }
264 
265     /**
266      * Creates this file as a folder.
267      */
268     @Override
269     protected void doCreateFolder() throws Exception {
270         final HttpMkcol request = setupRequest(new HttpMkcol(toUrlString((GenericURLFileName) getName())));
271         try {
272             // AutoClose the underlying HTTP connection which is held by the response object
273             try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
274                 // TODO: workaround due to PMD violation 'Empty try body - you could rename the resource to 'ignored'
275                 request.succeeded(res);
276             }
277         } catch (final FileSystemException fse) {
278             throw new FileSystemException("vfs.provider.webdav/create-collection.error", getName(), fse);
279         }
280     }
281 
282     /**
283      * Deletes the file.
284      */
285     @Override
286     protected void doDelete() throws Exception {
287         final HttpDelete request = setupRequest(new HttpDelete(toUrlString((GenericURLFileName) getName())));
288         // AutoClose the underlying HTTP connection which is held by the response object
289         try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
290             // TODO: workaround due to PMD violation 'Empty try body - you could rename the resource to 'ignored'
291             request.succeeded(res);
292         }
293     }
294 
295     /**
296      * Returns the properties of the Webdav resource.
297      */
298     @Override
299     protected Map<String, Object> doGetAttributes() throws Exception {
300         final Map<String, Object> attributes = new HashMap<>();
301         try {
302             final GenericURLFileName fileName = (GenericURLFileName) getName();
303             DavPropertySet properties = getProperties(fileName, DavConstants.PROPFIND_ALL_PROP,
304                     new DavPropertyNameSet(), false);
305             for (final DavProperty<?> property : properties) {
306                 attributes.put(property.getName().toString(), property.getValue());
307             }
308             properties = getPropertyNames(fileName);
309             for (DavProperty<?> property : properties) {
310                 if (!attributes.containsKey(property.getName().getName())) {
311                     property = getProperty(fileName, property.getName());
312                     if (property != null) {
313                         final Object name = property.getName();
314                         final Object value = property.getValue();
315                         if (name != null && value != null) {
316                             attributes.put(name.toString(), value);
317                         }
318                     }
319                 }
320             }
321             return attributes;
322         } catch (final Exception e) {
323             throw new FileSystemException("vfs.provider.webdav/get-attributes.error", getName(), e);
324         }
325     }
326 
327     /**
328      * Returns the size of the file content (in bytes).
329      */
330     @Override
331     protected long doGetContentSize() throws Exception {
332         final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETCONTENTLENGTH);
333         if (property != null) {
334             final String value = (String) property.getValue();
335             return Long.parseLong(value);
336         }
337         return 0;
338     }
339 
340     /**
341      * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return
342      * {@link FileType#IMAGINARY}.
343      */
344     @Override
345     protected long doGetLastModifiedTime() throws Exception {
346         final DavProperty<?> property = getProperty((GenericURLFileName) getName(), DavConstants.PROPERTY_GETLASTMODIFIED);
347         if (property != null) {
348             final String value = (String) property.getValue();
349             return DateUtils.parseDate(value).getTime();
350         }
351         return 0;
352     }
353 
354     @Override
355     protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
356         return new WebdavOutputStream(this);
357     }
358 
359     /**
360      * Determines the type of this file. Must not return null. The return value of this method is cached, so the
361      * implementation can be expensive.
362      */
363     @Override
364     protected FileType doGetType() throws Exception {
365         try {
366             return isDirectory((GenericURLFileName) getName()) ? FileType.FOLDER : FileType.FILE;
367         } catch (final FileNotFolderException | FileNotFoundException fnfe) {
368             return FileType.IMAGINARY;
369         }
370 
371     }
372 
373     /**
374      * Determines if this file can be written to. Is only called if {@link #doGetType} does not return
375      * {@link FileType#IMAGINARY}.
376      * <p>
377      * This implementation always returns true.
378      *
379      * @return true if the file is writable.
380      * @throws Exception if an error occurs.
381      */
382     @Override
383     protected boolean doIsWriteable() throws Exception {
384         return true;
385     }
386 
387     /**
388      * Lists the children of the file.
389      */
390     @Override
391     protected String[] doListChildren() throws Exception {
392         // use doListChildrenResolved for performance
393         return null;
394     }
395 
396     /**
397      * Lists the children of the file.
398      */
399     @Override
400     protected FileObject[] doListChildrenResolved() throws Exception {
401         HttpPropfind request = null;
402         try {
403             final GenericURLFileName name = (GenericURLFileName) getName();
404             if (isDirectory(name)) {
405                 final DavPropertyNameSet nameSet = new DavPropertyNameSet();
406                 nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_DISPLAYNAME));
407 
408                 request = new HttpPropfind(toUrlString(name), nameSet, DavConstants.DEPTH_1);
409 
410                 try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
411                     final List<Webdav4FileObject> vfs = new ArrayList<>();
412                     if (request.succeeded(res)) {
413                         final MultiStatusResponse[] responses = request.getResponseBodyAsMultiStatus(res).getResponses();
414 
415                         for (final MultiStatusResponse response : responses) {
416                             if (isCurrentFile(response.getHref(), name)) {
417                                 continue;
418                             }
419                             final String resourceName = resourceName(response.getHref());
420                             if (!resourceName.isEmpty()) {
421                                 final Webdav4FileObject fo = (Webdav4FileObject) FileObjectUtils.getAbstractFileObject(
422                                         getFileSystem().resolveFile(getFileSystem().getFileSystemManager()
423                                                 .resolveName(getName(), resourceName, NameScope.CHILD)));
424                                 vfs.add(fo);
425                             }
426                         }
427                     }
428                     return vfs.toArray(EMPTY_ARRAY);
429                 }
430             }
431             throw new FileNotFolderException(getName());
432         } catch (final FileNotFolderException fnfe) {
433             throw fnfe;
434         } catch (final DavException | IOException e) {
435             throw new FileSystemException(e.getMessage(), e);
436         }
437     }
438 
439     /**
440      * Rename the file.
441      */
442     @Override
443     protected void doRename(final FileObject newFile) throws Exception {
444         final String url = toUrlString((GenericURLFileName) getName());
445         final String dest = toUrlString((GenericURLFileName) newFile.getName(), false);
446         final HttpMove request = setupRequest(new HttpMove(url, dest, false));
447         // AutoClose the underlying HTTP connection which is held by the response object
448         try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
449             // TODO: workaround due to PMD violation 'Empty try body - you could rename the resource to 'ignored'
450             request.succeeded(res);
451         }
452     }
453 
454     /**
455      * Sets an attribute of this file. Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
456      */
457     @Override
458     protected void doSetAttribute(final String attrName, final Object value) throws Exception {
459         try {
460             final GenericURLFileName fileName = (GenericURLFileName) getName();
461             final String urlStr = toUrlString(fileName);
462             final DavPropertySet properties = new DavPropertySet();
463             final DavPropertyNameSet propertyNameSet = new DavPropertyNameSet();
464             final DavProperty<Object> property = new DefaultDavProperty<>(attrName, value, Namespace.EMPTY_NAMESPACE);
465             if (value != null) {
466                 properties.add(property);
467             } else {
468                 propertyNameSet.add(property.getName()); // remove property
469             }
470 
471             final HttpProppatch request = setupRequest(new HttpProppatch(urlStr, properties, propertyNameSet));
472             // AutoClose the underlying HTTP connection which is held by the response object
473             try (CloseableHttpResponse response = (CloseableHttpResponse) executeRequest(request)) {
474                 if (!request.succeeded(response)) {
475                     throw new FileSystemException("Property '" + attrName + "' could not be set.");
476                 }
477             }
478         } catch (final FileSystemException fse) {
479             throw fse;
480         } catch (final Exception e) {
481             throw new FileSystemException("vfs.provider.webdav/set-attributes", e, getName(), attrName);
482         }
483     }
484 
485     private HttpResponse executeRequest(final HttpUriRequest request) throws FileSystemException {
486         final HttpResponse response;
487 
488         try {
489             response = executeHttpUriRequest(request);
490             final int status = response.getStatusLine().getStatusCode();
491             if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
492                 EntityUtils.consume(response.getEntity());
493                 throw new FileNotFoundException(request.getURI());
494             }
495 
496             if (request instanceof BaseDavRequest) {
497                 ((BaseDavRequest) request).checkSuccess(response);
498             }
499 
500             return response;
501         } catch (final FileSystemException fse) {
502             throw fse;
503         } catch (final IOException e) {
504             throw new FileSystemException(e);
505         } catch (final DavException e) {
506             throw ExceptionConverter.generate(e);
507         }
508     }
509 
510     @Override
511     protected FileContentInfoFactory getFileContentInfoFactory() {
512         return new Webdav4FileContentInfoFactory();
513     }
514 
515     DavPropertySet getProperties(final GenericURLFileName name) throws FileSystemException {
516         return getProperties(name, DavConstants.PROPFIND_ALL_PROP, new DavPropertyNameSet(), false);
517     }
518 
519     DavPropertySet getProperties(final GenericURLFileName name, final DavPropertyNameSet nameSet, final boolean addEncoding)
520             throws FileSystemException {
521         return getProperties(name, DavConstants.PROPFIND_BY_PROPERTY, nameSet, addEncoding);
522     }
523 
524     DavPropertySet getProperties(final GenericURLFileName name, final int type, final DavPropertyNameSet nameSet,
525             final boolean addEncoding) throws FileSystemException {
526         try {
527             final String urlStr = toUrlString(name);
528             final HttpPropfind request = setupRequest(new HttpPropfind(urlStr, type, nameSet, DavConstants.DEPTH_0));
529             try (CloseableHttpResponse res = (CloseableHttpResponse) executeRequest(request)) {
530                 if (request.succeeded(res)) {
531                     final MultiStatus multiStatus = request.getResponseBodyAsMultiStatus(res);
532                     final MultiStatusResponse response = multiStatus.getResponses()[0];
533                     final DavPropertySet props = response.getProperties(HttpStatus.SC_OK);
534                     if (addEncoding) {
535                         final ContentType resContentType = ContentType.getOrDefault(res.getEntity());
536                         final DavProperty<String> prop = new DefaultDavProperty<>(RESPONSE_CHARSET,
537                                 resContentType.getCharset().name());
538                         props.add(prop);
539                     }
540                     return props;
541                 }
542                 return new DavPropertySet();
543             }
544         } catch (final FileSystemException fse) {
545             throw fse;
546         } catch (final Exception e) {
547             throw new FileSystemException("vfs.provider.webdav/get-property.error", e, getName(), name, type,
548                     nameSet.getContent(), addEncoding);
549         }
550     }
551 
552     DavProperty<?> getProperty(final GenericURLFileName fileName, final DavPropertyName name) throws FileSystemException {
553         final DavPropertyNameSet nameSet = new DavPropertyNameSet();
554         nameSet.add(name);
555         final DavPropertySet propertySet = getProperties(fileName, nameSet, false);
556         return propertySet.get(name);
557     }
558 
559     DavProperty<?> getProperty(final GenericURLFileName fileName, final String property) throws FileSystemException {
560         return getProperty(fileName, DavPropertyName.create(property));
561     }
562 
563     DavPropertySet getPropertyNames(final GenericURLFileName name) throws FileSystemException {
564         return getProperties(name, DavConstants.PROPFIND_PROPERTY_NAMES, new DavPropertyNameSet(), false);
565     }
566 
567     /**
568      * Convert the FileName to an encoded url String.
569      *
570      * @param name The FileName.
571      * @return The encoded URL String.
572      */
573     private String hrefString(final GenericURLFileName name) {
574         try {
575             final GenericURLFileName newFile = new GenericURLFileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(),
576                     null, null, name.getPath(), name.getType(), name.getQueryString());
577             return newFile.getURIEncoded(getUrlCharset());
578         } catch (final Exception e) {
579             return name.getURI();
580         }
581     }
582 
583     private boolean isCurrentFile(final String href, final GenericURLFileName fileName) {
584         String name = hrefString(fileName);
585         if (href.endsWith("/") && !name.endsWith("/")) {
586             name += "/";
587         }
588         return href.equals(name) || href.equals(fileName.getPath());
589     }
590 
591     private boolean isDirectory(final GenericURLFileName name) throws IOException {
592         try {
593             final DavProperty<?> property = getProperty(name, DavConstants.PROPERTY_RESOURCETYPE);
594             final Node node;
595             if (property != null && (node = (Node) property.getValue()) != null) {
596                 return node.getLocalName().equals(DavConstants.XML_COLLECTION);
597             }
598             return false;
599         } catch (final FileNotFoundException fse) {
600             throw new FileNotFolderException(name);
601         }
602     }
603 
604     void log(final Exception ex) {
605         // TODO Consider logging
606     }
607 
608     /**
609      * Returns the resource name from the path.
610      *
611      * @param path the path to the file.
612      * @return The resource name
613      */
614     private String resourceName(String path) {
615         if (path.endsWith("/")) {
616             path = path.substring(0, path.length() - 1);
617         }
618         final int i = path.lastIndexOf("/");
619         return i >= 0 ? path.substring(i + 1) : path;
620     }
621 
622     private <T extends HttpUriRequest> T setupRequest(final T request) {
623         // NOTE: *FileSystemConfigBuilder takes care of redirect option and user agent.
624         request.addHeader("Cache-control", "no-cache");
625         request.addHeader("Cache-store", "no-store");
626         request.addHeader("Pragma", "no-cache");
627         request.addHeader("Expires", "0");
628         return request;
629     }
630 
631     /**
632      * Converts the given URLFileName to an encoded URL String to internally use in real DAV operations.
633      *
634      * @param name The FileName.
635      * @return The encoded URL String.
636      */
637     String toUrlString(final GenericURLFileName name) {
638         return toUrlString(name, true);
639     }
640 
641     /**
642      * Converts the given URLFileName to an encoded URL String to internally use in real DAV operations.
643      *
644      * @param name The FileName.
645      * @param includeUserInfo true if user information should be included.
646      * @return The encoded URL String.
647      */
648     private String toUrlString(final GenericURLFileName name, final boolean includeUserInfo) {
649         String user = null;
650         String password = null;
651         if (includeUserInfo) {
652             user = name.getUserName();
653             password = name.getPassword();
654         }
655         try {
656             final GenericURLFileName newFile = new Webdav4FileName(getInternalURI().getScheme(), name.getHostName(), name.getPort(), name.getDefaultPort(),
657                     user, password, name.getPath(), name.getType(), name.getQueryString(),
658                     builder.getAppendTrailingSlash(getFileSystem().getFileSystemOptions()));
659             return newFile.getURIEncoded(getUrlCharset());
660         } catch (final Exception e) {
661             return name.getURI();
662         }
663     }
664 }