package org.lognavigator.service; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.text.MessageFormat; import java.util.Date; import java.util.Set; import java.util.TreeSet; import net.schmizz.sshj.common.IOUtils; import org.lognavigator.bean.FileInfo; import org.lognavigator.bean.LogAccessConfig; import org.lognavigator.bean.OsType; import org.lognavigator.exception.LogAccessException; import static org.lognavigator.util.Constants.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.FileCopyUtils; /** * Abstract class defining common methods for LogAccessService based on shell commands */ public abstract class AbstractShellLogAccessService implements LogAccessService { private static final String DIRECTORY_MARKER = "4000"; private static final String GET_PERL_INFO_COMMAND = "echo DIRECTORY_OK && perl -v"; private static final String DIRECTORY_OK_MARKER = "DIRECTORY_OK"; private static final String PERL_INSTALLED_MARKER = "this is perl"; private static final String LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND = "ls -a --group-directories-first | head -1"; private static final String LS_WITH_GROUP_DIRECTORIES_FIRST_SUPPORTED_MARKER = ".\n"; @Autowired protected ConfigService configService; /** * Check and return if host referenced by 'logAccessConfig' has perl installed * @param logAccessConfig log access config to test * @return true if referenced host has perl installed * @throws LogAccessException if a technical error occurs */ protected boolean isPerlInstalled(LogAccessConfig logAccessConfig) throws LogAccessException { if (logAccessConfig.isPerlInstalled() == null) { try { // Execute command to know if perl is installed InputStream resultStream = executeCommand(logAccessConfig.getId(), GET_PERL_INFO_COMMAND); // Check if perl is installed String result = FileCopyUtils.copyToString(new InputStreamReader(resultStream)); if (!result.contains(DIRECTORY_OK_MARKER)) { throw new LogAccessException("Configuration is invalid : directory " + logAccessConfig.getDirectory() + " does not exist"); } boolean isPerlInstalled = result.toLowerCase().contains(PERL_INSTALLED_MARKER); // Update logAccessConfig to cache the information (and not execute command every time) logAccessConfig.setPerlInstalled(isPerlInstalled); } catch (IOException ioe) { throw new LogAccessException("Error while reading response of command : " + GET_PERL_INFO_COMMAND, ioe); } } return logAccessConfig.isPerlInstalled(); } /** * Check and return if host referenced by 'logAccessConfig' supports command {@value #LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND} * @param logAccessConfig log access config to test * @return true if referenced host supports command {@value #LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND} * @throws LogAccessException if a technical error occurs */ protected boolean isLsWithGroupDirectoriesFirstSupported(LogAccessConfig logAccessConfig) throws LogAccessException { if (logAccessConfig.isLsWithGroupDirectoriesFirstSupported() == null) { try { // Execute command to know if command {@value #LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND} is supported InputStream resultStream = executeCommand(logAccessConfig.getId(), LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND); // Check if command {@value #LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND} is supported String result = FileCopyUtils.copyToString(new InputStreamReader(resultStream)); boolean isLsWithGroupDirectoriesFirstSupported = result.equals(LS_WITH_GROUP_DIRECTORIES_FIRST_SUPPORTED_MARKER); // Update logAccessConfig to cache the information (and not execute command every time) logAccessConfig.setLsWithGroupDirectoriesFirstSupported(isLsWithGroupDirectoriesFirstSupported); } catch (IOException ioe) { throw new LogAccessException("Error while reading response of command : " + LS_WITH_GROUP_DIRECTORIES_FIRST_CHECK_COMMAND, ioe); } } return logAccessConfig.isLsWithGroupDirectoriesFirstSupported(); } @Override public Set<FileInfo> listFiles(String logAccessConfigId, String subPath) throws LogAccessException { // Get the LogAccessConfig LogAccessConfig logAccessConfig = configService.getLogAccessConfig(logAccessConfigId); // If Perl is not installed : execute a simple 'ls' command if (!isPerlInstalled(logAccessConfig)) { return listFilesUsingNativeSystem(logAccessConfig, subPath); } // Avoid trailing / at end of subPath if (subPath != null && subPath.endsWith("/") && subPath.length() > 1) { subPath = subPath.substring(0, subPath.length() - 1); } // Construct perl list command based path and maximum file count String path = (subPath != null) ? subPath : "."; OsType osType = getOSType(logAccessConfig); String listCommandPattern = isLsWithGroupDirectoriesFirstSupported(logAccessConfig) ? PERL_LIST_COMMAND_LINUX_GROUP_DIRECTORIES_FIRST : PERL_LIST_COMMAND_FALLBACK; String listCommand = MessageFormat.format(listCommandPattern, path, configService.getFileListMaxCount()); if (osType == OsType.WINDOWS) { listCommand = listCommand.replace("\"", "\\\"") .replace('\'', '"'); } // Execute perl command InputStream resultStream = executeCommand(logAccessConfigId, listCommand); BufferedReader resultReader = new BufferedReader(new InputStreamReader(resultStream, Charset.forName("UTF-8"))); // Parse the result lines to build FileInfo list Set<FileInfo> fileInfos = new TreeSet<FileInfo>(); StringBuilder potentialErrorMessage = new StringBuilder(); String line = null; try { while ((line = resultReader.readLine()) != null) { potentialErrorMessage.append(line).append("\n"); // Parse the line String[] lineTokens = line.split(" "); boolean isDirectory = lineTokens[0].equals(DIRECTORY_MARKER); long fileSize = Long.parseLong(lineTokens[1]); Date lastModified = new Date(Long.parseLong(lineTokens[2])); String fileName = lineTokens[3]; int tokenIndex = 4; while (tokenIndex < lineTokens.length) { fileName += " " + lineTokens[tokenIndex]; ++tokenIndex; } // Compute relative path prefix String relativePathPrefix = ""; if ("/".equals(subPath)) { relativePathPrefix = subPath; } else if (subPath != null) { relativePathPrefix = subPath + "/"; } // Build FileInfo bean FileInfo fileInfo = new FileInfo(); fileInfo.setDirectory(isDirectory); fileInfo.setFileSize(isDirectory ? 0L : fileSize); fileInfo.setLastModified(lastModified); fileInfo.setFileName(fileName); fileInfo.setLogAccessType(logAccessConfig.getType()); fileInfo.setRelativePath(relativePathPrefix + fileName); fileInfos.add(fileInfo); } // Return list of files return fileInfos; } catch (RuntimeException e) { try { potentialErrorMessage.append(FileCopyUtils.copyToString(resultReader)); } catch (IOException ioe) {} throw new LogAccessException("Error while executing list command.\n " + potentialErrorMessage.toString(), e); } catch (IOException e) { throw new LogAccessException("I/O Error while listing files in path '" + subPath + "'\n" + e.getMessage(), e); } finally { IOUtils.closeQuietly(resultReader); } } /** * list files of a path, using native underlying system * @see #listFiles(String, String) */ protected abstract Set<FileInfo> listFilesUsingNativeSystem(LogAccessConfig logAccessConfig, String subPath) throws LogAccessException; /** * Compute and return which OS Type is isntalled on host referenced by 'logAccessConfig' * @param logAccessConfig log access config to test * @return which OS Type is installed on host referenced by 'logAccessConfig' * @throws LogAccessException if a technical error occurs */ protected abstract OsType getOSType(LogAccessConfig logAccessConfig) throws LogAccessException; }