View Javadoc
1   package org.apache.maven.wagon.providers.webdav;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.httpclient.HttpException;
23  import org.apache.commons.httpclient.HttpStatus;
24  import org.apache.jackrabbit.webdav.DavConstants;
25  import org.apache.jackrabbit.webdav.DavException;
26  import org.apache.jackrabbit.webdav.MultiStatus;
27  import org.apache.jackrabbit.webdav.MultiStatusResponse;
28  import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
29  import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
30  import org.apache.jackrabbit.webdav.property.DavProperty;
31  import org.apache.jackrabbit.webdav.property.DavPropertyName;
32  import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
33  import org.apache.jackrabbit.webdav.property.DavPropertySet;
34  import org.apache.maven.wagon.PathUtils;
35  import org.apache.maven.wagon.ResourceDoesNotExistException;
36  import org.apache.maven.wagon.TransferFailedException;
37  import org.apache.maven.wagon.WagonConstants;
38  import org.apache.maven.wagon.authorization.AuthorizationException;
39  import org.apache.maven.wagon.repository.Repository;
40  import org.codehaus.plexus.util.FileUtils;
41  import org.codehaus.plexus.util.StringUtils;
42  import org.w3c.dom.Node;
43  
44  import java.io.File;
45  import java.io.IOException;
46  import java.net.URLDecoder;
47  import java.util.ArrayList;
48  import java.util.List;
49  
50  /**
51   * <p>WebDavWagon</p>
52   * <p/>
53   * <p>Allows using a webdav remote repository for downloads and deployments</p>
54   *
55   * @author <a href="mailto:hisidro@exist.com">Henry Isidro</a>
56   * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
57   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
58   * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
59   * @plexus.component role="org.apache.maven.wagon.Wagon"
60   * role-hint="dav"
61   * instantiation-strategy="per-lookup"
62   */
63  public class WebDavWagon
64      extends AbstractHttpClientWagon
65  {
66      protected static final String CONTINUE_ON_FAILURE_PROPERTY = "wagon.webdav.continueOnFailure";
67  
68      private final boolean continueOnFailure = Boolean.getBoolean( CONTINUE_ON_FAILURE_PROPERTY );
69  
70      /**
71       * Defines the protocol mapping to use.
72       * <p/>
73       * First string is the user definition way to define a webdav url,
74       * the second string is the internal representation of that url.
75       * <p/>
76       * NOTE: The order of the mapping becomes the search order.
77       */
78      private static final String[][] protocolMap =
79          new String[][]{ { "dav:http://", "http://" },    /* maven 2.0.x url string format. (violates URI spec) */
80              { "dav:https://", "https://" },  /* maven 2.0.x url string format. (violates URI spec) */
81              { "dav+http://", "http://" },    /* URI spec compliant (protocol+transport) */
82              { "dav+https://", "https://" },  /* URI spec compliant (protocol+transport) */
83              { "dav://", "http://" },         /* URI spec compliant (protocol only) */
84              { "davs://", "https://" }        /* URI spec compliant (protocol only) */ };
85  
86      /**
87       * This wagon supports directory copying
88       *
89       * @return <code>true</code> always
90       */
91      public boolean supportsDirectoryCopy()
92      {
93          return true;
94      }
95  
96      /**
97       * Create directories in server as needed.
98       * They are created one at a time until the whole path exists.
99       *
100      * @param dir path to be created in server from repository basedir
101      * @throws IOException
102      * @throws HttpException
103      * @throws TransferFailedException
104      */
105     protected void mkdirs( String dir )
106         throws IOException
107     {
108         Repository repository = getRepository();
109         String basedir = repository.getBasedir();
110 
111         String baseUrl = repository.getProtocol() + "://" + repository.getHost();
112         if ( repository.getPort() != WagonConstants.UNKNOWN_PORT )
113         {
114             baseUrl += ":" + repository.getPort();
115         }
116 
117         // create relative path that will always have a leading and trailing slash
118         String relpath = FileUtils.normalize( getPath( basedir, dir ) + "/" );
119 
120         PathNavigator navigator = new PathNavigator( relpath );
121 
122         // traverse backwards until we hit a directory that already exists (OK/NOT_ALLOWED), or that we were able to
123         // create (CREATED), or until we get to the top of the path
124         int status = SC_NULL;
125         do
126         {
127             String url = baseUrl + "/" + navigator.getPath();
128             status = doMkCol( url );
129             if ( status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED
130                 || status == HttpStatus.SC_METHOD_NOT_ALLOWED )
131             {
132                 break;
133             }
134         }
135         while ( navigator.backward() );
136 
137         // traverse forward creating missing directories
138         while ( navigator.forward() )
139         {
140             String url = baseUrl + "/" + navigator.getPath();
141             status = doMkCol( url );
142             if ( status != HttpStatus.SC_OK && status != HttpStatus.SC_CREATED )
143             {
144                 throw new IOException( "Unable to create collection: " + url + "; status code = " + status );
145             }
146         }
147     }
148 
149     private int doMkCol( String url )
150         throws IOException
151     {
152         MkColMethod method = null;
153         try
154         {
155             method = new MkColMethod( url );
156             return execute( method );
157         }
158         finally
159         {
160             if ( method != null )
161             {
162                 method.releaseConnection();
163             }
164         }
165     }
166 
167     /**
168      * Copy a directory from local system to remote webdav server
169      *
170      * @param sourceDirectory      the local directory
171      * @param destinationDirectory the remote destination
172      * @throws TransferFailedException
173      * @throws ResourceDoesNotExistException
174      * @throws AuthorizationException
175      */
176     public void putDirectory( File sourceDirectory, String destinationDirectory )
177         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
178     {
179         for ( File file : sourceDirectory.listFiles() )
180         {
181             if ( file.isDirectory() )
182             {
183                 putDirectory( file, destinationDirectory + "/" + file.getName() );
184             }
185             else
186             {
187                 String target = destinationDirectory + "/" + file.getName();
188 
189                 put( file, target );
190             }
191         }
192     }
193 
194     private boolean isDirectory( String url )
195         throws IOException, DavException
196     {
197         DavPropertyNameSet nameSet = new DavPropertyNameSet();
198         nameSet.add( DavPropertyName.create( DavConstants.PROPERTY_RESOURCETYPE ) );
199 
200         PropFindMethod method = null;
201         try
202         {
203             method = new PropFindMethod( url, nameSet, DavConstants.DEPTH_0 );
204             execute( method );
205             if ( method.succeeded() )
206             {
207                 MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
208                 MultiStatusResponse response = multiStatus.getResponses()[0];
209                 DavPropertySet propertySet = response.getProperties( HttpStatus.SC_OK );
210                 DavProperty<?> property = propertySet.get( DavConstants.PROPERTY_RESOURCETYPE );
211                 if ( property != null )
212                 {
213                     Node node = (Node) property.getValue();
214                     return node.getLocalName().equals( DavConstants.XML_COLLECTION );
215                 }
216             }
217             return false;
218         }
219         finally
220         {
221             if ( method != null )
222             {
223                 method.releaseConnection();
224             }
225         }
226     }
227 
228     public List<String> getFileList( String destinationDirectory )
229         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
230     {
231         String repositoryUrl = repository.getUrl();
232         String url = repositoryUrl + ( repositoryUrl.endsWith( "/" ) ? "" : "/" ) + destinationDirectory;
233 
234         PropFindMethod method = null;
235         try
236         {
237             if ( isDirectory( url ) )
238             {
239                 DavPropertyNameSet nameSet = new DavPropertyNameSet();
240                 nameSet.add( DavPropertyName.create( DavConstants.PROPERTY_DISPLAYNAME ) );
241 
242                 method = new PropFindMethod( url, nameSet, DavConstants.DEPTH_1 );
243                 int status = execute( method );
244                 if ( method.succeeded() )
245                 {
246                     ArrayList<String> dirs = new ArrayList<String>();
247                     MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
248 
249                     for ( int i = 0; i < multiStatus.getResponses().length; i++ )
250                     {
251 
252                         MultiStatusResponse response = multiStatus.getResponses()[i];
253 
254                         String entryUrl = response.getHref();
255                         String fileName = PathUtils.filename( URLDecoder.decode( entryUrl ) );
256                         if ( entryUrl.endsWith( "/" ) )
257                         {
258                             if ( i == 0 )
259                             {
260                                 //by design jackrabbit webdav sticks parent directory as the first entry
261                                 // so we need to ignore this entry
262                                 // http://www.nabble.com/Extra-entry-in-get-file-list-with-jackrabbit-webdav-td21262786.html
263                                 // http://www.webdav.org/specs/rfc4918.html#rfc.section.9.1
264                                 continue;
265                             }
266 
267                             //extract "dir/" part of "path.to.dir/"
268                             fileName = PathUtils.filename( PathUtils.dirname( URLDecoder.decode( entryUrl ) ) ) + "/";
269                         }
270 
271                         if ( !StringUtils.isEmpty( fileName ) )
272                         {
273                             dirs.add( fileName );
274                         }
275                     }
276                     return dirs;
277                 }
278 
279                 if ( status == HttpStatus.SC_NOT_FOUND )
280                 {
281                     throw new ResourceDoesNotExistException( "Destination directory does not exist: " + url );
282                 }
283             }
284         }
285         catch ( DavException e )
286         {
287             throw new TransferFailedException( e.getMessage(), e );
288         }
289         catch ( IOException e )
290         {
291             throw new TransferFailedException( e.getMessage(), e );
292         }
293         finally
294         {
295             if ( method != null )
296             {
297                 method.releaseConnection();
298             }
299         }
300         throw new ResourceDoesNotExistException(
301             "Destination path exists but is not a " + "WebDAV collection (directory): " + url );
302     }
303 
304     public String getURL( Repository repository )
305     {
306         String url = repository.getUrl();
307 
308         // Process mappings first.
309         for ( String[] entry : protocolMap )
310         {
311             String protocol = entry[0];
312             if ( url.startsWith(protocol) )
313             {
314                 return entry[1] + url.substring( protocol.length() );
315             }
316         }
317 
318         // No mapping trigger? then just return as-is.
319         return url;
320     }
321 
322 
323     public void put( File source, String resourceName )
324         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
325     {
326         try
327         {
328             super.put( source, resourceName );
329         }
330         catch ( TransferFailedException e )
331         {
332             if ( continueOnFailure )
333             {
334                 // TODO use a logging mechanism here or a fireTransferWarning
335                 System.out.println(
336                     "WARN: Skip unable to transfer '" + resourceName + "' from '" + source.getPath() + "' due to "
337                         + e.getMessage() );
338             }
339             else
340             {
341                 throw e;
342             }
343         }
344     }
345 }