| package org.apache.maven.wagon.providers.ssh.jsch; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.io.File; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.maven.wagon.InputData; |
| import org.apache.maven.wagon.OutputData; |
| import org.apache.maven.wagon.PathUtils; |
| import org.apache.maven.wagon.ResourceDoesNotExistException; |
| import org.apache.maven.wagon.TransferFailedException; |
| import org.apache.maven.wagon.authentication.AuthenticationException; |
| import org.apache.maven.wagon.authorization.AuthorizationException; |
| import org.apache.maven.wagon.events.TransferEvent; |
| import org.apache.maven.wagon.providers.ssh.ScpHelper; |
| import org.apache.maven.wagon.repository.RepositoryPermissions; |
| import org.apache.maven.wagon.resource.Resource; |
| |
| import com.jcraft.jsch.ChannelSftp; |
| import com.jcraft.jsch.JSchException; |
| import com.jcraft.jsch.SftpATTRS; |
| import com.jcraft.jsch.SftpException; |
| |
| /** |
| * SFTP protocol wagon. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| * |
| * @todo [BP] add compression flag |
| * @todo see if SftpProgressMonitor allows us to do streaming (without it, we can't do checksums as the input stream is lost) |
| * |
| * @plexus.component role="org.apache.maven.wagon.Wagon" |
| * role-hint="sftp" |
| * instantiation-strategy="per-lookup" |
| */ |
| public class SftpWagon |
| extends AbstractJschWagon |
| { |
| private static final String SFTP_CHANNEL = "sftp"; |
| |
| private static final int S_IFDIR = 0x4000; |
| |
| private static final long MILLIS_PER_SEC = 1000L; |
| |
| private ChannelSftp channel; |
| |
| public void closeConnection() |
| { |
| if ( channel != null ) |
| { |
| channel.disconnect(); |
| } |
| super.closeConnection(); |
| } |
| |
| public void openConnectionInternal() |
| throws AuthenticationException |
| { |
| super.openConnectionInternal(); |
| |
| try |
| { |
| channel = (ChannelSftp) session.openChannel( SFTP_CHANNEL ); |
| |
| channel.connect(); |
| } |
| catch ( JSchException e ) |
| { |
| throw new AuthenticationException( "Error connecting to remote repository: " + getRepository().getUrl(), |
| e ); |
| } |
| } |
| |
| private void returnToParentDirectory( Resource resource ) |
| { |
| try |
| { |
| String dir = ScpHelper.getResourceDirectory( resource.getName() ); |
| String[] dirs = PathUtils.dirnames( dir ); |
| for ( String d : dirs ) |
| { |
| channel.cd( ".." ); |
| } |
| } |
| catch ( SftpException e ) |
| { |
| fireTransferDebug( "Error returning to parent directory: " + e.getMessage() ); |
| } |
| } |
| |
| private void putFile( File source, Resource resource, RepositoryPermissions permissions ) |
| throws SftpException, TransferFailedException |
| { |
| resource.setContentLength( source.length() ); |
| |
| resource.setLastModified( source.lastModified() ); |
| |
| String filename = ScpHelper.getResourceFilename( resource.getName() ); |
| |
| firePutStarted( resource, source ); |
| |
| channel.put( source.getAbsolutePath(), filename ); |
| |
| postProcessListeners( resource, source, TransferEvent.REQUEST_PUT ); |
| |
| if ( permissions != null && permissions.getGroup() != null ) |
| { |
| setGroup( filename, permissions ); |
| } |
| |
| if ( permissions != null && permissions.getFileMode() != null ) |
| { |
| setFileMode( filename, permissions ); |
| } |
| |
| firePutCompleted( resource, source ); |
| } |
| |
| private void setGroup( String filename, RepositoryPermissions permissions ) |
| { |
| try |
| { |
| int group = Integer.valueOf( permissions.getGroup() ).intValue(); |
| channel.chgrp( group, filename ); |
| } |
| catch ( NumberFormatException e ) |
| { |
| // TODO: warning level |
| fireTransferDebug( "Not setting group: must be a numerical GID for SFTP" ); |
| } |
| catch ( SftpException e ) |
| { |
| fireTransferDebug( "Not setting group: " + e.getMessage() ); |
| } |
| } |
| |
| private void setFileMode( String filename, RepositoryPermissions permissions ) |
| { |
| try |
| { |
| int mode = getOctalMode( permissions.getFileMode() ); |
| channel.chmod( mode, filename ); |
| } |
| catch ( NumberFormatException e ) |
| { |
| // TODO: warning level |
| fireTransferDebug( "Not setting mode: must be a numerical mode for SFTP" ); |
| } |
| catch ( SftpException e ) |
| { |
| fireTransferDebug( "Not setting mode: " + e.getMessage() ); |
| } |
| } |
| |
| private void mkdirs( String resourceName, int mode ) |
| throws SftpException, TransferFailedException |
| { |
| String[] dirs = PathUtils.dirnames( resourceName ); |
| for ( String dir : dirs ) |
| { |
| mkdir( dir, mode ); |
| |
| channel.cd( dir ); |
| } |
| } |
| |
| private void mkdir( String dir, int mode ) |
| throws TransferFailedException, SftpException |
| { |
| try |
| { |
| SftpATTRS attrs = channel.stat( dir ); |
| if ( ( attrs.getPermissions() & S_IFDIR ) == 0 ) |
| { |
| throw new TransferFailedException( "Remote path is not a directory: " + dir ); |
| } |
| } |
| catch ( SftpException e ) |
| { |
| // doesn't exist, make it and try again |
| channel.mkdir( dir ); |
| if ( mode != -1 ) |
| { |
| try |
| { |
| channel.chmod( mode, dir ); |
| } |
| catch ( SftpException e1 ) |
| { |
| // for some extrange reason we recive this exception, |
| // even when chmod success |
| } |
| } |
| } |
| } |
| |
| private SftpATTRS changeToRepositoryDirectory( String dir, String filename ) |
| throws ResourceDoesNotExistException, SftpException |
| { |
| // This must be called first to ensure that if the file doesn't exist it throws an exception |
| SftpATTRS attrs; |
| try |
| { |
| channel.cd( repository.getBasedir() ); |
| |
| if ( dir.length() > 0 ) |
| { |
| channel.cd( dir ); |
| } |
| |
| if ( filename.length() == 0 ) |
| { |
| filename = "."; |
| } |
| |
| attrs = channel.stat( filename ); |
| } |
| catch ( SftpException e ) |
| { |
| if ( e.toString().trim().endsWith( "No such file" ) ) |
| { |
| throw new ResourceDoesNotExistException( e.toString(), e ); |
| } |
| else if ( e.toString().trim().contains( "Can't change directory" ) ) |
| { |
| throw new ResourceDoesNotExistException( e.toString(), e ); |
| } |
| else |
| { |
| throw e; |
| } |
| } |
| return attrs; |
| } |
| |
| public void putDirectory( File sourceDirectory, String destinationDirectory ) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException |
| { |
| final RepositoryPermissions permissions = repository.getPermissions(); |
| |
| try |
| { |
| channel.cd( "/" ); |
| |
| String basedir = getRepository().getBasedir(); |
| int directoryMode = getDirectoryMode( permissions ); |
| |
| mkdirs( basedir + "/", directoryMode ); |
| |
| fireTransferDebug( "Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as " |
| + destinationDirectory ); |
| |
| mkdirs( destinationDirectory, directoryMode ); |
| ftpRecursivePut( sourceDirectory, null, ScpHelper.getResourceFilename( destinationDirectory ), |
| directoryMode ); |
| } |
| catch ( SftpException e ) |
| { |
| String msg = |
| "Error occurred while deploying '" + sourceDirectory.getAbsolutePath() + "' " + "to remote repository: " |
| + getRepository().getUrl() + ": " + e.getMessage(); |
| |
| throw new TransferFailedException( msg, e ); |
| } |
| } |
| |
| private void ftpRecursivePut( File sourceFile, String prefix, String fileName, int directoryMode ) |
| throws TransferFailedException, SftpException |
| { |
| final RepositoryPermissions permissions = repository.getPermissions(); |
| |
| if ( sourceFile.isDirectory() ) |
| { |
| // ScpHelper.getResourceFilename( destinationDirectory ) - could return empty string |
| if ( !fileName.equals( "." ) && !fileName.equals( "" ) ) |
| { |
| prefix = getFileName( prefix, fileName ); |
| mkdir( fileName, directoryMode ); |
| channel.cd( fileName ); |
| } |
| |
| File[] files = sourceFile.listFiles(); |
| if ( files != null && files.length > 0 ) |
| { |
| // Directories first, then files. Let's go deep early. |
| for ( File file : files ) |
| { |
| if ( file.isDirectory() ) |
| { |
| ftpRecursivePut( file, prefix, file.getName(), directoryMode ); |
| } |
| } |
| for ( File file : files ) |
| { |
| if ( !file.isDirectory() ) |
| { |
| ftpRecursivePut( file, prefix, file.getName(), directoryMode ); |
| } |
| } |
| } |
| |
| channel.cd( ".." ); |
| } |
| else |
| { |
| Resource resource = ScpHelper.getResource( getFileName( prefix, fileName ) ); |
| |
| firePutInitiated( resource, sourceFile ); |
| |
| putFile( sourceFile, resource, permissions ); |
| } |
| } |
| |
| private String getFileName( String prefix, String fileName ) |
| { |
| if ( prefix != null ) |
| { |
| prefix = prefix + "/" + fileName; |
| } |
| else |
| { |
| prefix = fileName; |
| } |
| return prefix; |
| } |
| |
| public List<String> getFileList( String destinationDirectory ) |
| throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException |
| { |
| if ( destinationDirectory.length() == 0 ) |
| { |
| destinationDirectory = "."; |
| } |
| |
| String filename = ScpHelper.getResourceFilename( destinationDirectory ); |
| |
| String dir = ScpHelper.getResourceDirectory( destinationDirectory ); |
| |
| // we already setuped the root directory. Ignore beginning / |
| if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR ) |
| { |
| dir = dir.substring( 1 ); |
| } |
| |
| try |
| { |
| SftpATTRS attrs = changeToRepositoryDirectory( dir, filename ); |
| if ( ( attrs.getPermissions() & S_IFDIR ) == 0 ) |
| { |
| throw new TransferFailedException( "Remote path is not a directory:" + dir ); |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| List<ChannelSftp.LsEntry> fileList = channel.ls( filename ); |
| List<String> files = new ArrayList<String>( fileList.size() ); |
| for ( ChannelSftp.LsEntry entry : fileList ) |
| { |
| String name = entry.getFilename(); |
| if ( entry.getAttrs().isDir() ) |
| { |
| if ( !name.equals( "." ) && !name.equals( ".." ) ) |
| { |
| if ( !name.endsWith( "/" ) ) |
| { |
| name += "/"; |
| } |
| files.add( name ); |
| } |
| } |
| else |
| { |
| files.add( name ); |
| } |
| } |
| return files; |
| } |
| catch ( SftpException e ) |
| { |
| String msg = |
| "Error occurred while listing '" + destinationDirectory + "' " + "on remote repository: " |
| + getRepository().getUrl() + ": " + e.getMessage(); |
| |
| throw new TransferFailedException( msg, e ); |
| } |
| } |
| |
| public boolean resourceExists( String resourceName ) |
| throws TransferFailedException, AuthorizationException |
| { |
| String filename = ScpHelper.getResourceFilename( resourceName ); |
| |
| String dir = ScpHelper.getResourceDirectory( resourceName ); |
| |
| // we already setuped the root directory. Ignore beginning / |
| if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR ) |
| { |
| dir = dir.substring( 1 ); |
| } |
| |
| try |
| { |
| changeToRepositoryDirectory( dir, filename ); |
| |
| return true; |
| } |
| catch ( ResourceDoesNotExistException e ) |
| { |
| return false; |
| } |
| catch ( SftpException e ) |
| { |
| String msg = |
| "Error occurred while looking for '" + resourceName + "' " + "on remote repository: " |
| + getRepository().getUrl() + ": " + e.getMessage(); |
| |
| throw new TransferFailedException( msg, e ); |
| } |
| } |
| |
| protected void cleanupGetTransfer( Resource resource ) |
| { |
| returnToParentDirectory( resource ); |
| } |
| |
| protected void cleanupPutTransfer( Resource resource ) |
| { |
| returnToParentDirectory( resource ); |
| } |
| |
| protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output ) |
| throws TransferFailedException |
| { |
| RepositoryPermissions permissions = getRepository().getPermissions(); |
| |
| String filename = ScpHelper.getResourceFilename( resource.getName() ); |
| if ( permissions != null && permissions.getGroup() != null ) |
| { |
| setGroup( filename, permissions ); |
| } |
| |
| if ( permissions != null && permissions.getFileMode() != null ) |
| { |
| setFileMode( filename, permissions ); |
| } |
| } |
| |
| public void fillInputData( InputData inputData ) |
| throws TransferFailedException, ResourceDoesNotExistException |
| { |
| Resource resource = inputData.getResource(); |
| |
| String filename = ScpHelper.getResourceFilename( resource.getName() ); |
| |
| String dir = ScpHelper.getResourceDirectory( resource.getName() ); |
| |
| // we already setuped the root directory. Ignore beginning / |
| if ( dir.length() > 0 && dir.charAt( 0 ) == ScpHelper.PATH_SEPARATOR ) |
| { |
| dir = dir.substring( 1 ); |
| } |
| |
| try |
| { |
| SftpATTRS attrs = changeToRepositoryDirectory( dir, filename ); |
| |
| long lastModified = attrs.getMTime() * MILLIS_PER_SEC; |
| resource.setContentLength( attrs.getSize() ); |
| |
| resource.setLastModified( lastModified ); |
| |
| inputData.setInputStream( channel.get( filename ) ); |
| } |
| catch ( SftpException e ) |
| { |
| handleGetException( resource, e ); |
| } |
| } |
| |
| public void fillOutputData( OutputData outputData ) |
| throws TransferFailedException |
| { |
| int directoryMode = getDirectoryMode( getRepository().getPermissions() ); |
| |
| Resource resource = outputData.getResource(); |
| |
| try |
| { |
| channel.cd( "/" ); |
| |
| String basedir = getRepository().getBasedir(); |
| mkdirs( basedir + "/", directoryMode ); |
| |
| mkdirs( resource.getName(), directoryMode ); |
| |
| String filename = ScpHelper.getResourceFilename( resource.getName() ); |
| outputData.setOutputStream( channel.put( filename ) ); |
| } |
| catch ( TransferFailedException e ) |
| { |
| fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); |
| |
| throw e; |
| } |
| catch ( SftpException e ) |
| { |
| fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); |
| |
| String msg = |
| "Error occurred while deploying '" + resource.getName() + "' " + "to remote repository: " |
| + getRepository().getUrl() + ": " + e.getMessage(); |
| |
| throw new TransferFailedException( msg, e ); |
| } |
| } |
| |
| /** |
| * @param permissions repository's permissions |
| * @return the directory mode for the repository or <code>-1</code> if it |
| * wasn't set |
| */ |
| public int getDirectoryMode( RepositoryPermissions permissions ) |
| { |
| int ret = -1; |
| |
| if ( permissions != null ) |
| { |
| ret = getOctalMode( permissions.getDirectoryMode() ); |
| } |
| |
| return ret; |
| } |
| |
| public int getOctalMode( String mode ) |
| { |
| int ret; |
| try |
| { |
| ret = Integer.valueOf( mode, 8 ).intValue(); |
| } |
| catch ( NumberFormatException e ) |
| { |
| // TODO: warning level |
| fireTransferDebug( "the file mode must be a numerical mode for SFTP" ); |
| ret = -1; |
| } |
| return ret; |
| } |
| } |