All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.foreach.across.modules.filemanager.services.SpringIntegrationSftpFolderResource Maven / Gradle / Ivy

The newest version!
package com.foreach.across.modules.filemanager.services;

import com.foreach.across.modules.filemanager.business.*;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
import org.springframework.util.AntPathMatcher;

import java.util.*;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Steven Gentens
 * @since 2.3.0
 */
@Slf4j
public class SpringIntegrationSftpFolderResource extends SpringIntegrationFolderResource
{
	private final FolderDescriptor folderDescriptor;
	private final SftpRemoteFileTemplate remoteFileTemplate;

	SpringIntegrationSftpFolderResource( @NonNull FolderDescriptor folderDescriptor,
	                                     @NonNull SftpRemoteFileTemplate remoteFileTemplate ) {
		super( folderDescriptor, remoteFileTemplate );
		this.folderDescriptor = folderDescriptor;
		this.remoteFileTemplate = remoteFileTemplate;
	}

	@Override
	public boolean exists() {
		return retrieveRemoteFile( getPath() ).exists();
	}

	protected boolean exists( ChannelSftp client ) {
		return retrieveRemoteFile( client, getPath() ) != null;
	}

	@Override
	public Optional getParentFolderResource() {
		return folderDescriptor.getParentFolderDescriptor()
		                       .map( fd -> new SpringIntegrationSftpFolderResource( fd, remoteFileTemplate ) );
	}

	@Override
	public FileRepositoryResource getResource( @NonNull String relativePath ) {
		if ( relativePath.isEmpty() || "/".equals( relativePath ) ) {
			return this;
		}

		if ( relativePath.endsWith( "/" ) ) {
			return new SpringIntegrationSftpFolderResource( folderDescriptor.createFolderDescriptor( relativePath ), remoteFileTemplate );
		}

		FileDescriptor fileDescriptor = folderDescriptor.createFileDescriptor( relativePath );
		String actualPath = SpringIntegrationSftpFileResource.getPath( fileDescriptor );
		SFTPFile ftpFile = remoteFileTemplate.exists( actualPath )
				? retrieveRemoteFile( actualPath )
				: null;

		return new SpringIntegrationSftpFileResource( fileDescriptor, ftpFile, remoteFileTemplate );
	}

	@Override
	@SuppressWarnings("Duplicates")
	public Collection findResources( @NonNull String pattern ) {
		try (Session session = remoteFileTemplate.getSession();) {
			ChannelSftp client = (ChannelSftp) session.getClientInstance();
			return findResources( pattern, client );
		}
	}

	@SuppressWarnings("Duplicates")
	protected Collection findResources( @NonNull String pattern, ChannelSftp client ) {
		if ( exists( client ) ) {
			Set resources = new LinkedHashSet<>();
			AntPathMatcher pathMatcher = new AntPathMatcher( "/" );
			String p = StringUtils.startsWith( pattern, "/" ) ? pattern.substring( 1 ) : pattern;
			boolean matchOnlyDirectories = StringUtils.endsWith( p, "/" );

			if ( matchOnlyDirectories ) {
				p = p.substring( 0, p.length() - 1 );
			}

			if ( !p.contains( "*" ) && !p.contains( "?" ) ) {
				return resolveExactPath( p, client );
			}

			BiPredicate keyMatcher = ( candidateObjectName, antPattern ) -> {
				String patternToMatch = !StringUtils.isBlank( antPattern ) ? StringUtils.prependIfMissing( antPattern, "/" ) : antPattern;
				String path = patternToMatch.endsWith( "/" ) ? candidateObjectName : StringUtils.removeEnd( candidateObjectName, "/" );
				if ( pathMatcher.match( patternToMatch, path ) ) {
					return !matchOnlyDirectories || candidateObjectName.endsWith( "/" );
				}
				return false;
			};

			if ( !p.contains( "**" ) && !p.contains( "?" ) ) {
				return resolvePatternForListing( p, matchOnlyDirectories, keyMatcher, client );
			}

			findResourcesWithMatchingKeys( keyMatcher, resources, getPath() + getValidPrefix( p ), StringUtils.prependIfMissing( p, "/" ),
			                               matchOnlyDirectories, client );

			return resources;
		}

		return Collections.emptyList();
	}

	private Collection resolveExactPath( String p, ChannelSftp client ) {
		String pathToSearch = StringUtils.removeEnd( getPath(), "/" ) + "/" + StringUtils.removeStart( p, "/" );
		SFTPFile ftpFile = retrieveRemoteFile( client, pathToSearch );

		if ( ftpFile == null ) {
			return Collections.emptyList();
		}

		if ( ftpFile.isDirectory() ) {
			return Collections.singletonList(
					new SpringIntegrationSftpFolderResource( FolderDescriptor.of( folderDescriptor.getRepositoryId(), pathToSearch ),
					                                         remoteFileTemplate ) );
		}
		return Collections.singletonList( createFileResource( ftpFile, StringUtils.removeStart( pathToSearch, "/" ) ) );
	}

	@SuppressWarnings("Duplicates")
	private Collection resolvePatternForListing( String p,
	                                                                     boolean matchOnlyDirectories,
	                                                                     BiPredicate keyMatcher,
	                                                                     ChannelSftp client ) {
		String validPrefix = getValidPrefix( p );
		String remainingPattern = StringUtils.removeStart( p, validPrefix );

		String pathToSearch =
				StringUtils.removeEnd( getPath(), "/" ) + "/" + StringUtils.removeStart( validPrefix, "/" );
		// not yet the final part
		if ( remainingPattern.contains( "/" ) ) {
			int nextSlash = StringUtils.indexOf( remainingPattern, "/" );
			String nextPart = StringUtils.substring( remainingPattern, 0, nextSlash + 1 );
			String keyPattern = StringUtils.removeEnd( getPath(), "/" ) + "/" + StringUtils.removeStart( nextPart, "/" );
			String withoutNextPart = StringUtils.removeStart( remainingPattern, nextPart );
			return retrieveFoldersForPath( client, pathToSearch )
					.stream()
					.filter( f -> {
						String pathToTest = StringUtils.appendIfMissing( f.getPath(), "/" );
						return keyMatcher.test( pathToTest, keyPattern );
					} )
					.map( f -> {
						String pathToFind = matchOnlyDirectories ? StringUtils.appendIfMissing( withoutNextPart, "/" ) : withoutNextPart;
						return f.findResources( pathToFind, client );
					} )
					.flatMap( Collection::stream )
					.collect( Collectors.toSet() );
		}

		SpringIntegrationSftpFolderResource baseResource = validPrefix.isEmpty() ? this : (SpringIntegrationSftpFolderResource) getFolderResource(
				validPrefix );

		String keyPattern = StringUtils.removeEnd( baseResource.getPath(), "/" ) + "/" + StringUtils.removeStart( remainingPattern, "/" );
		if ( matchOnlyDirectories ) {
			return baseResource.retrieveFoldersForPath( client, pathToSearch )
			                   .stream()
			                   .filter( f -> {
				                   String pathToTest = StringUtils.appendIfMissing( f.getPath(), "/" );
				                   return keyMatcher.test( pathToTest, keyPattern );
			                   } )
			                   .collect( Collectors.toSet() );
		}

		return Stream.concat( baseResource.retrieveFoldersForPath( client, pathToSearch ).stream(),
		                      baseResource.retrieveFilesForPath( client, pathToSearch ).stream() )
		             .filter( f -> {
			             if ( f instanceof FolderResource ) {
				             String folderPath = SpringIntegrationFolderResource.getPath( ( (FolderResource) f ).getDescriptor() );
				             String pathToTest = StringUtils.appendIfMissing( folderPath, "/" );
				             return keyMatcher.test( pathToTest, keyPattern );
			             }
			             String pathToTest = SpringIntegrationFileResource.getPath( ( (FileResource) f ).getDescriptor() );
			             return keyMatcher.test( pathToTest, keyPattern );
		             } ).collect( Collectors.toSet() );
	}

	private SFTPFile retrieveRemoteFile( String pathToSearch ) {
		return remoteFileTemplate.executeWithClient( client -> retrieveRemoteFile( client, pathToSearch ) );
	}

	private SFTPFile retrieveRemoteFile( ChannelSftp client, String path ) {
		return new SFTPFile( remoteFileTemplate, path );
	}

	private void findResourcesWithMatchingKeys( BiPredicate keyMatcher,
	                                            Set resources,
	                                            String currentPath,
	                                            String keyPattern, boolean matchOnlyDirectories, ChannelSftp client ) {
		if ( !keyPattern.endsWith( "/" ) ) {
			retrieveFilesForPath( client, currentPath )
					.stream()
					.filter( file -> {
						String path = SpringIntegrationFtpFileResource.getPath( file.getDescriptor() );
						return keyMatcher.test( path, keyPattern );
					} )
					.forEach( resources::add );
		}

		String pathToLookFor = currentPath;
		String patternBasedOnPath = keyPattern;
		if ( !keyPattern.startsWith( "**" ) && !keyPattern.startsWith( "?" ) ) {
			String nextValidPrefix = getValidPrefix( keyPattern );
			String remainingKeyPattern = StringUtils.removeStart( keyPattern, nextValidPrefix );
			pathToLookFor = currentPath + StringUtils.prependIfMissing( nextValidPrefix, "/" );
			pathToLookFor = pathToLookFor.replaceAll( "//", "/" );
			patternBasedOnPath = remainingKeyPattern;
		}
		String newKeyPattern = patternBasedOnPath;

		if ( !newKeyPattern.contains( "/" ) && !matchOnlyDirectories && newKeyPattern.contains( "?" ) ) {
			String matchingPattern = pathToLookFor + StringUtils.removeStart( newKeyPattern, "/" );
			List filesForPath = retrieveFilesForPath( client, pathToLookFor );
			filesForPath.stream()
			            .filter( file -> {
				            String path = SpringIntegrationFileResource.getPath( file.getDescriptor() );
				            return keyMatcher.test( path, matchingPattern );
			            } )
			            .forEach( resources::add );
		}

		List folderResources = retrieveFoldersForPath( client, pathToLookFor );
		folderResources.stream()
		               .filter( folder -> {
			               String path = StringUtils.appendIfMissing( SpringIntegrationSftpFolderResource.getPath( folder.getDescriptor() ), "/" );
			               return keyMatcher.test( path, newKeyPattern );
		               } )
		               .forEach( resources::add );

		folderResources
				.forEach(
						f -> {
							String recursivePattern = matchOnlyDirectories ? StringUtils.appendIfMissing( newKeyPattern, "/" ) : newKeyPattern;
							resources.addAll( f.findResources( recursivePattern, client ) );
						}
				);

	}

	@SuppressWarnings("Duplicates")
	private String getValidPrefix( String keyPattern ) {
		int starIndex = keyPattern.indexOf( '*' );
		int markIndex = keyPattern.indexOf( '?' );
		int index = Math.min(
				starIndex == -1 ? keyPattern.length() : starIndex,
				markIndex == -1 ? keyPattern.length() : markIndex
		);
		String beforeIndex = keyPattern.substring( 0, index );
		return beforeIndex.contains( "/" ) ? beforeIndex.substring( 0, beforeIndex.lastIndexOf( '/' ) + 1 ) : "";
	}

	@SuppressWarnings("unchecked")
	private List retrieveFoldersForPath( ChannelSftp client, String path ) {
		List ftpFolderNames = new ArrayList<>();
		try {
			Vector files = client.ls( path );
			for ( ChannelSftp.LsEntry entry : files ) {
				if ( !entry.getFilename().equals( "." ) && !entry.getFilename().equals( ".." ) && entry.getAttrs().isDir() ) {
					ftpFolderNames.add( entry.getFilename() );
				}
			}
		}
		catch ( SftpException e ) {
			LOG.error( "Unexpected error whilst listing directories for path '{}'. Falling back to no directories found.", path, e );
		}

		return ftpFolderNames.stream()
		                     .map( fileName -> new SpringIntegrationSftpFolderResource(
				                     FolderDescriptor.of( folderDescriptor.getRepositoryId(), getPath() ).createFolderDescriptor( fileName ),
				                     remoteFileTemplate ) )
		                     .collect( Collectors.toList() );
	}

	@SuppressWarnings("unchecked")
	private List retrieveFilesForPath( ChannelSftp client, String path ) {
		List ftpFileNames = new ArrayList<>();

		try {
			Vector files = client.ls( path );
			for ( ChannelSftp.LsEntry entry : files ) {
				if ( !entry.getAttrs().isDir() ) {
					ftpFileNames.add( entry.getFilename() );
				}
			}
		}
		catch ( SftpException e ) {
			LOG.error( "Unexpected error whilst listing files for path '{}'. Falling back to no directories found.", path, e );
		}

		return ftpFileNames.stream()
		                   .map( fileName -> createFileResource( new SFTPFile( remoteFileTemplate, path + "/" + fileName ) ) ) // iffy full path creation maybe
		                   .collect( Collectors.toList() );
	}

	private SpringIntegrationSftpFileResource createFileResource( SFTPFile file ) {
		return createFileResource( file, file.getName() );
	}

	private SpringIntegrationSftpFileResource createFileResource( SFTPFile file, String fullPath ) {
		String fileName = fullPath;
		String folderName = getPath();
		int lastIndexOfSlash = fileName.lastIndexOf( "/" );
		if ( lastIndexOfSlash != -1 ) {
			fileName = fileName.substring( lastIndexOfSlash + 1 );
			folderName = fullPath.replace( fileName, "" );
		}
		return new SpringIntegrationSftpFileResource( FileDescriptor.of( folderDescriptor.getRepositoryId(), folderName, fileName ),
		                                              file,
		                                              remoteFileTemplate );
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy