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