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 java.io.File;
23  import java.io.IOException;
24  import java.net.URLDecoder;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.apache.commons.httpclient.HttpException;
29  import org.apache.commons.httpclient.HttpStatus;
30  import org.apache.jackrabbit.webdav.DavConstants;
31  import org.apache.jackrabbit.webdav.DavException;
32  import org.apache.jackrabbit.webdav.MultiStatus;
33  import org.apache.jackrabbit.webdav.MultiStatusResponse;
34  import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
35  import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
36  import org.apache.jackrabbit.webdav.property.DavProperty;
37  import org.apache.jackrabbit.webdav.property.DavPropertyName;
38  import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
39  import org.apache.jackrabbit.webdav.property.DavPropertySet;
40  import org.apache.maven.wagon.PathUtils;
41  import org.apache.maven.wagon.ResourceDoesNotExistException;
42  import org.apache.maven.wagon.TransferFailedException;
43  import org.apache.maven.wagon.WagonConstants;
44  import org.apache.maven.wagon.authorization.AuthorizationException;
45  import org.apache.maven.wagon.repository.Repository;
46  import org.apache.maven.wagon.shared.http.AbstractHttpClientWagon;
47  import org.codehaus.plexus.util.FileUtils;
48  import org.codehaus.plexus.util.StringUtils;
49  import org.w3c.dom.Node;
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   *
61   * @plexus.component role="org.apache.maven.wagon.Wagon"
62   *   role-hint="dav"
63   *   instantiation-strategy="per-lookup"
64   */
65  public class WebDavWagon
66      extends AbstractHttpClientWagon
67  {
68      /**
69       * Defines the protocol mapping to use.
70       *
71       * First string is the user definition way to define a webdav url,
72       * the second string is the internal representation of that url.
73       *
74       * NOTE: The order of the mapping becomes the search order.
75       */
76      private static final String[][] protocolMap = new String[][] {
77          { "dav:http://", "http://" },    /* maven 2.0.x url string format. (violates URI spec) */
78          { "dav:https://", "https://" },  /* maven 2.0.x url string format. (violates URI spec) */
79          { "dav+http://", "http://" },    /* URI spec compliant (protocol+transport) */
80          { "dav+https://", "https://" },  /* URI spec compliant (protocol+transport) */
81          { "dav://", "http://" },         /* URI spec compliant (protocol only) */
82          { "davs://", "https://" }        /* URI spec compliant (protocol only) */
83      };
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 HttpException
102      * @throws TransferFailedException
103      */
104     protected void mkdirs( String dir ) throws HttpException, 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 ) throws HttpException, IOException
148     {
149         MkColMethod method = null;
150         try
151         {
152             method = new MkColMethod( url );
153             return execute( method );
154         }
155         finally
156         {
157             if ( method != null )
158             {
159                 method.releaseConnection();
160             }
161         }
162     }
163 
164     /**
165      * Copy a directory from local system to remote webdav server
166      *
167      * @param sourceDirectory      the local directory
168      * @param destinationDirectory the remote destination
169      * @throws TransferFailedException
170      * @throws ResourceDoesNotExistException
171      * @throws AuthorizationException
172      */
173     public void putDirectory( File sourceDirectory, String destinationDirectory )
174         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
175     {
176         File[] listFiles = sourceDirectory.listFiles();
177 
178         for ( int i = 0; i < listFiles.length; i++ )
179         {
180             if ( listFiles[i].isDirectory() )
181             {
182                 putDirectory( listFiles[i], destinationDirectory + "/" + listFiles[i].getName() );
183             }
184             else
185             {
186                 String target = destinationDirectory + "/" + listFiles[i].getName();
187 
188                 put( listFiles[i], target );
189             }
190         }
191 
192     }
193 
194     private boolean isDirectory( String url ) throws IOException, DavException
195     {
196         DavPropertyNameSet nameSet = new DavPropertyNameSet();
197         nameSet.add( DavPropertyName.create( DavConstants.PROPERTY_RESOURCETYPE ) );
198 
199         PropFindMethod method = null;
200         try
201         {
202             method = new PropFindMethod( url, nameSet, DavConstants.DEPTH_0 );
203             execute( method );
204             if ( method.succeeded() )
205             {
206                 MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
207                 MultiStatusResponse response = multiStatus.getResponses()[0];
208                 DavPropertySet propertySet = response.getProperties( HttpStatus.SC_OK );
209                 DavProperty property = propertySet.get( DavConstants.PROPERTY_RESOURCETYPE );
210                 if ( property != null )
211                 {
212                     Node node = (Node) property.getValue();
213                     return node.getLocalName().equals( DavConstants.XML_COLLECTION );
214                 }
215             }
216             return false;
217         }
218         finally
219         {
220             if ( method != null )
221             {
222                 method.releaseConnection();
223             }
224         }
225     }
226 
227     public List getFileList( String destinationDirectory )
228         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
229     {
230         String url = getRepository().getUrl() + '/' + 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 dirs = new ArrayList();
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( "Destination path exists but is not a "
299                         + "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 ( int i = 0; i < protocolMap.length; i++ )
308         {
309             String protocol = protocolMap[i][0];
310             if ( url.startsWith( protocol ) )
311             {
312                 return protocolMap[i][1] + url.substring( protocol.length() );
313             }
314         }
315 
316         // No mapping trigger? then just return as-is.
317         return url;
318     }
319 }