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