View Javadoc

1   package org.apache.maven.wagon.providers.ftp;
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.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Calendar;
29  import java.util.List;
30  
31  import org.apache.commons.net.ProtocolCommandEvent;
32  import org.apache.commons.net.ProtocolCommandListener;
33  import org.apache.commons.net.ftp.FTP;
34  import org.apache.commons.net.ftp.FTPClient;
35  import org.apache.commons.net.ftp.FTPFile;
36  import org.apache.commons.net.ftp.FTPReply;
37  import org.apache.maven.wagon.ConnectionException;
38  import org.apache.maven.wagon.InputData;
39  import org.apache.maven.wagon.OutputData;
40  import org.apache.maven.wagon.PathUtils;
41  import org.apache.maven.wagon.ResourceDoesNotExistException;
42  import org.apache.maven.wagon.StreamWagon;
43  import org.apache.maven.wagon.TransferFailedException;
44  import org.apache.maven.wagon.WagonConstants;
45  import org.apache.maven.wagon.authentication.AuthenticationException;
46  import org.apache.maven.wagon.authentication.AuthenticationInfo;
47  import org.apache.maven.wagon.authorization.AuthorizationException;
48  import org.apache.maven.wagon.repository.RepositoryPermissions;
49  import org.apache.maven.wagon.resource.Resource;
50  import org.codehaus.plexus.util.IOUtil;
51  
52  /**
53   * FtpWagon 
54   *
55   * @version $Id: FtpWagon.java 745732 2009-02-19 05:23:07Z brett $
56   * 
57   * @plexus.component role="org.apache.maven.wagon.Wagon" 
58   *   role-hint="ftp"
59   *   instantiation-strategy="per-lookup"
60   */
61  public class FtpWagon
62      extends StreamWagon
63  {
64      private FTPClient ftp;
65      
66      /** @plexus.configuration default-value="true" */
67      private boolean passiveMode = true;
68  
69      public boolean isPassiveMode()
70      {
71          return passiveMode;
72      }
73  
74      public void setPassiveMode( boolean passiveMode )
75      {
76          this.passiveMode = passiveMode;
77      }
78  
79      protected void openConnectionInternal()
80          throws ConnectionException, AuthenticationException
81      {
82          AuthenticationInfo authInfo = getAuthenticationInfo();
83  
84          if ( authInfo == null )
85          {
86              throw new IllegalArgumentException( "Authentication Credentials cannot be null for FTP protocol" );
87          }
88  
89          if ( authInfo.getUserName() == null )
90          {
91              authInfo.setUserName( System.getProperty( "user.name" ) );
92          }
93  
94          String username = authInfo.getUserName();
95  
96          String password = authInfo.getPassword();
97  
98          if ( username == null )
99          {
100             throw new AuthenticationException( "Username not specified for repository " + getRepository().getId() );
101         }
102         if ( password == null )
103         {
104             throw new AuthenticationException( "Password not specified for repository " + getRepository().getId() );
105         }
106 
107         String host = getRepository().getHost();
108 
109         ftp = new FTPClient();
110         ftp.setDefaultTimeout( getTimeout() );
111         ftp.setDataTimeout( getTimeout() );
112         
113         ftp.addProtocolCommandListener( new PrintCommandListener( this ) );
114 
115         try
116         {
117             if ( getRepository().getPort() != WagonConstants.UNKNOWN_PORT )
118             {
119                 ftp.connect( host, getRepository().getPort() );
120             }
121             else
122             {
123                 ftp.connect( host );
124             }
125 
126             // After connection attempt, you should check the reply code to
127             // verify
128             // success.
129             int reply = ftp.getReplyCode();
130 
131             if ( !FTPReply.isPositiveCompletion( reply ) )
132             {
133                 ftp.disconnect();
134 
135                 throw new AuthenticationException( "FTP server refused connection." );
136             }
137         }
138         catch ( IOException e )
139         {
140             if ( ftp.isConnected() )
141             {
142                 try
143                 {
144                     fireSessionError( e );
145 
146                     ftp.disconnect();
147                 }
148                 catch ( IOException f )
149                 {
150                     // do nothing
151                 }
152             }
153 
154             throw new AuthenticationException( "Could not connect to server.", e );
155         }
156 
157         try
158         {
159             if ( !ftp.login( username, password ) )
160             {
161                 throw new AuthenticationException( "Cannot login to remote system" );
162             }
163 
164             fireSessionDebug( "Remote system is " + ftp.getSystemName() );
165 
166             // Set to binary mode.
167             ftp.setFileType( FTP.BINARY_FILE_TYPE );
168             ftp.setListHiddenFiles( true );
169 
170             // Use passive mode as default because most of us are
171             // behind firewalls these days.
172             if ( isPassiveMode() )
173             {                
174                 ftp.enterLocalPassiveMode();
175             }
176         }
177         catch ( IOException e )
178         {
179             throw new ConnectionException( "Cannot login to remote system", e );
180         }
181     }
182 
183     protected void firePutCompleted( Resource resource, File file )
184     {
185         try
186         {
187             // TODO [BP]: verify the order is correct
188             ftp.completePendingCommand();
189 
190             RepositoryPermissions permissions = repository.getPermissions();
191 
192             if ( permissions != null && permissions.getGroup() != null )
193             {
194                 // ignore failures
195                 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + resource.getName() );
196             }
197 
198             if ( permissions != null && permissions.getFileMode() != null )
199             {
200                 // ignore failures
201                 ftp.sendSiteCommand( "CHMOD " + permissions.getFileMode() + " " + resource.getName() );
202             }
203         }
204         catch ( IOException e )
205         {
206             // TODO: handle
207             // michal I am not sure  what error means in that context
208             // I think that we will be able to recover or simply we will fail later on
209         }
210 
211         super.firePutCompleted( resource, file );
212     }
213 
214     protected void fireGetCompleted( Resource resource, File localFile )
215     {
216         try
217         {
218             ftp.completePendingCommand();
219         }
220         catch ( IOException e )
221         {
222             // TODO: handle
223             // michal I am not sure  what error means in that context
224             // actually I am not even sure why we have to invoke that command
225             // I think that we will be able to recover or simply we will fail later on
226         }
227         super.fireGetCompleted( resource, localFile );
228     }
229 
230     public void closeConnection()
231         throws ConnectionException
232     {
233         if ( ftp != null && ftp.isConnected() )
234         {
235             try
236             {
237                 // This is a NPE rethink shutting down the streams
238                 ftp.disconnect();
239             }
240             catch ( IOException e )
241             {
242                 throw new ConnectionException( "Failed to close connection to FTP repository", e );
243             }
244         }
245     }
246 
247     public void fillOutputData( OutputData outputData )
248         throws TransferFailedException
249     {
250         OutputStream os;
251 
252         Resource resource = outputData.getResource();
253 
254         RepositoryPermissions permissions = repository.getPermissions();
255 
256         try
257         {
258             if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
259             {
260                 throw new TransferFailedException(
261                     "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
262             }
263 
264             String[] dirs = PathUtils.dirnames( resource.getName() );
265 
266             for ( int i = 0; i < dirs.length; i++ )
267             {
268                 boolean dirChanged = ftp.changeWorkingDirectory( dirs[i] );
269 
270                 if ( !dirChanged )
271                 {
272                     // first, try to create it
273                     boolean success = ftp.makeDirectory( dirs[i] );
274 
275                     if ( success )
276                     {
277                         if ( permissions != null && permissions.getGroup() != null )
278                         {
279                             // ignore failures
280                             ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() + " " + dirs[i] );
281                         }
282 
283                         if ( permissions != null && permissions.getDirectoryMode() != null )
284                         {
285                             // ignore failures
286                             ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() + " " + dirs[i] );
287                         }
288 
289                         dirChanged = ftp.changeWorkingDirectory( dirs[i] );
290                     }
291                 }
292 
293                 if ( !dirChanged )
294                 {
295                     throw new TransferFailedException( "Unable to create directory " + dirs[i] );
296                 }
297             }
298 
299             // we come back to original basedir so
300             // FTP wagon is ready for next requests
301             if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
302             {
303                 throw new TransferFailedException( "Unable to return to the base directory" );
304             }
305 
306             os = ftp.storeFileStream( resource.getName() );
307 
308             if ( os == null )
309             {
310                 String msg = "Cannot transfer resource:  '" + resource
311                     + "'. Output stream is null. FTP Server response: " + ftp.getReplyString();
312 
313                 throw new TransferFailedException( msg );
314 
315             }
316 
317             fireTransferDebug( "resource = " + resource );
318 
319         }
320         catch ( IOException e )
321         {
322             throw new TransferFailedException( "Error transferring over FTP", e );
323         }
324 
325         outputData.setOutputStream( os );
326 
327     }
328 
329     // ----------------------------------------------------------------------
330     //
331     // ----------------------------------------------------------------------
332 
333     public void fillInputData( InputData inputData )
334         throws TransferFailedException, ResourceDoesNotExistException
335     {
336         InputStream is;
337 
338         Resource resource = inputData.getResource();
339 
340         try
341         {
342             ftpChangeDirectory( resource );
343 
344             String filename = PathUtils.filename( resource.getName() );
345             FTPFile[] ftpFiles = ftp.listFiles( filename );
346 
347             if ( ftpFiles == null || ftpFiles.length <= 0 )
348             {
349                 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
350             }
351 
352             long contentLength = ftpFiles[0].getSize();
353 
354             //@todo check how it works! javadoc of common login says:
355             // Returns the file timestamp. This usually the last modification time.
356             //
357             Calendar timestamp = ftpFiles[0].getTimestamp();
358             long lastModified = timestamp != null ? timestamp.getTimeInMillis() : 0;
359 
360             resource.setContentLength( contentLength );
361 
362             resource.setLastModified( lastModified );
363 
364             is = ftp.retrieveFileStream( filename );
365         }
366         catch ( IOException e )
367         {
368             throw new TransferFailedException( "Error transferring file via FTP", e );
369         }
370 
371         inputData.setInputStream( is );
372     }
373 
374     private void ftpChangeDirectory( Resource resource )
375         throws IOException, TransferFailedException, ResourceDoesNotExistException
376     {
377         if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
378         {
379             throw new ResourceDoesNotExistException(
380                 "Required directory: '" + getRepository().getBasedir() + "' " + "is missing" );
381         }
382 
383         String[] dirs = PathUtils.dirnames( resource.getName() );
384 
385         for ( int i = 0; i < dirs.length; i++ )
386         {
387             boolean dirChanged = ftp.changeWorkingDirectory( dirs[i] );
388 
389             if ( !dirChanged )
390             {
391                 String msg = "Resource " + resource + " not found. Directory " + dirs[i] + " does not exist";
392 
393                 throw new ResourceDoesNotExistException( msg );
394             }
395         }
396     }
397 
398     public class PrintCommandListener
399         implements ProtocolCommandListener
400     {
401         private FtpWagon wagon;
402 
403         public PrintCommandListener( FtpWagon wagon )
404         {
405             this.wagon = wagon;
406         }
407 
408         public void protocolCommandSent( ProtocolCommandEvent event )
409         {
410             wagon.fireSessionDebug( "Command sent: " + event.getMessage() );
411 
412         }
413 
414         public void protocolReplyReceived( ProtocolCommandEvent event )
415         {
416             wagon.fireSessionDebug( "Reply received: " + event.getMessage() );
417         }
418     }
419 
420     protected void fireSessionDebug( String msg )
421     {
422         super.fireSessionDebug( msg );
423     }
424 
425     public List getFileList( String destinationDirectory )
426         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
427     {
428         Resource resource = new Resource( destinationDirectory );
429         
430         try 
431         {
432             ftpChangeDirectory( resource );
433     
434             String filename = PathUtils.filename( resource.getName() );
435             FTPFile[] ftpFiles = ftp.listFiles( filename );
436     
437             if ( ftpFiles == null || ftpFiles.length <= 0 )
438             {
439                 throw new ResourceDoesNotExistException( "Could not find file: '" + resource + "'" );
440             }
441             
442             List ret = new ArrayList();
443             for( int i=0; i < ftpFiles.length; i++ )
444             {
445                 String name = ftpFiles[i].getName();
446                 
447                 if ( ftpFiles[i].isDirectory() && !name.endsWith( "/" ) )
448                 {
449                     name += "/";
450                 }
451                 
452                 ret.add( name );
453             }
454             
455             return ret;
456         }
457         catch ( IOException e )
458         {
459             throw new TransferFailedException( "Error transferring file via FTP", e );
460         }
461     }
462 
463     public boolean resourceExists( String resourceName )
464         throws TransferFailedException, AuthorizationException
465     {
466         Resource resource = new Resource( resourceName );
467 
468         try
469         {
470             ftpChangeDirectory( resource );
471 
472             String filename = PathUtils.filename( resource.getName() );
473             int status = ftp.stat( filename );
474 
475             return ( ( status == FTPReply.FILE_STATUS ) || ( status == FTPReply.FILE_STATUS_OK )
476                             || ( status == FTPReply.SYSTEM_STATUS ) );
477         }
478         catch ( IOException e )
479         {
480             throw new TransferFailedException( "Error transferring file via FTP", e );
481         }
482         catch ( ResourceDoesNotExistException e )
483         {
484             return false;
485         }
486     }
487     
488     public boolean supportsDirectoryCopy()
489     {
490         return true;
491     }
492 
493     public void putDirectory( File sourceDirectory, String destinationDirectory )
494         throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
495     {
496 
497         // Change to root.
498         try
499         {
500             if ( !ftp.changeWorkingDirectory( getRepository().getBasedir() ) )
501             {
502                 throw new TransferFailedException( "Required directory: '" + getRepository().getBasedir() + "' "
503                                 + "is missing" );
504             }
505         }
506         catch ( IOException e )
507         {
508             throw new TransferFailedException( "Cannot change to root path " + getRepository().getBasedir() );
509         }
510 
511         fireTransferDebug( "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as "
512                         + destinationDirectory );
513         ftpRecursivePut( sourceDirectory, destinationDirectory );
514     }
515 
516     private void ftpRecursivePut( File sourceFile, String fileName ) throws TransferFailedException
517     {
518         final RepositoryPermissions permissions = repository.getPermissions();
519 
520         fireTransferDebug( "processing = " + sourceFile.getAbsolutePath() + " as " + fileName );
521 
522         if ( sourceFile.isDirectory() )
523         {
524             if ( !fileName.equals( "." ) )
525             {
526                 try
527                 {
528                     // change directory if it already exists.
529                     if ( !ftp.changeWorkingDirectory( fileName ) )
530                     {
531                         // first, try to create it
532                         if ( ftp.makeDirectory( fileName ) )
533                         {
534                             if ( permissions != null )
535                             {
536                                 // Process permissions; note that if we get errors or exceptions here, they are ignored.
537                                 // This appears to be a conscious decision, based on other parts of this code.
538                                 String group = permissions.getGroup();
539                                 if ( group != null )
540                                 {
541                                     try
542                                     {
543                                         ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
544                                     }
545                                     catch ( IOException e )
546                                     {
547                                     }
548                                 }
549                                 String mode = permissions.getDirectoryMode();
550                                 if ( mode != null )
551                                 {
552                                     try
553                                     {
554                                         ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
555                                     }
556                                     catch ( IOException e )
557                                     {
558                                     }
559                                 }
560                             }
561 
562                             if ( !ftp.changeWorkingDirectory( fileName ) )
563                             {
564                                 throw new TransferFailedException( "Unable to change cwd on ftp server to " + fileName
565                                                 + " when processing " + sourceFile.getAbsolutePath() );
566                             }
567                         }
568                         else
569                         {
570                             throw new TransferFailedException( "Unable to create directory " + fileName
571                                             + " when processing " + sourceFile.getAbsolutePath() );
572                         }
573                     }
574                 }
575                 catch ( IOException e )
576                 {
577                     throw new TransferFailedException( "IOException caught while processing path at "
578                                     + sourceFile.getAbsolutePath(), e );
579                 }
580             }
581 
582             File[] files = sourceFile.listFiles();
583             if ( files != null && files.length > 0 )
584             {
585                 fireTransferDebug( "listing children of = " + sourceFile.getAbsolutePath() + " found " + files.length );
586 
587                 // Directories first, then files. Let's go deep early.
588                 for ( int i = 0; i < files.length; i++ )
589                 {
590                     if ( files[i].isDirectory() )
591                     {
592                         ftpRecursivePut( files[i], files[i].getName() );
593                     }
594                 }
595                 for ( int i = 0; i < files.length; i++ )
596                 {
597                     if ( !files[i].isDirectory() )
598                     {
599                         ftpRecursivePut( files[i], files[i].getName() );
600                     }
601                 }
602             }
603 
604             // Step back up a directory once we're done with the contents of this one.
605             try
606             {
607                 ftp.changeToParentDirectory();
608             }
609             catch ( IOException e )
610             {
611                 throw new TransferFailedException( "IOException caught while attempting to step up to parent directory"
612                                                    + " after successfully processing " + sourceFile.getAbsolutePath(),
613                                                    e );
614             }
615         }
616         else
617         {
618             // Oh how I hope and pray, in denial, but today I am still just a file.
619             
620             FileInputStream sourceFileStream = null;
621             try
622             {
623                 sourceFileStream = new FileInputStream( sourceFile );
624                 
625                 // It's a file. Upload it in the current directory.
626                 if ( ftp.storeFile( fileName, sourceFileStream ) )
627                 {
628                     if ( permissions != null )
629                     {
630                         // Process permissions; note that if we get errors or exceptions here, they are ignored.
631                         // This appears to be a conscious decision, based on other parts of this code.
632                         String group = permissions.getGroup();
633                         if ( group != null )
634                             try
635                             {
636                                 ftp.sendSiteCommand( "CHGRP " + permissions.getGroup() );
637                             }
638                             catch ( IOException e )
639                             {
640                             }
641                         String mode = permissions.getFileMode();
642                         if ( mode != null )
643                             try
644                             {
645                                 ftp.sendSiteCommand( "CHMOD " + permissions.getDirectoryMode() );
646                             }
647                             catch ( IOException e )
648                             {
649                             }
650                     }
651                 }
652                 else
653                 {
654                     String msg =
655                         "Cannot transfer resource:  '" + sourceFile.getAbsolutePath() + "' FTP Server response: "
656                                         + ftp.getReplyString();
657                     throw new TransferFailedException( msg );
658                 }
659             }
660             catch ( IOException e )
661             {
662                 throw new TransferFailedException( "IOException caught while attempting to upload "
663                                 + sourceFile.getAbsolutePath(), e );
664             }
665             finally
666             {
667                 IOUtil.close( sourceFileStream );
668             }
669 
670         }
671 
672         fireTransferDebug( "completed = " + sourceFile.getAbsolutePath() );
673     }
674 }