package org.lognavigator.service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
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.LogAccessConfig.LogAccessType;
import org.lognavigator.exception.LogAccessException;
import org.lognavigator.util.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
/**
* Service which manages HTTP connections and commands to remote hosts
* where logs are exposed by an Apache Httpd server, using DirectoryIndex directive
*/
@Service
@Qualifier("httpd")
public class HttpdLogAccessService implements LogAccessService {
private static final String TABLE_START = "<pre>";
private static final String TABLE_END = "</pre>";
private static final String LINK_HREF_END = "\"";
private static final String LINK_HREF_START = "<a href=\"";
private static final String DATE_FORMAT_NUMERIC = "yyyy-MM-dd HH:mm";
private static final String DATE_FORMAT_LITTERAL = "dd-MMM-yyyy HH:mm";
private static final String LINK_END_TAG = "</a>";
private static final String DIRECTORY_SEPARATOR = "/";
private static final String LAST_MODIFIED_SORT = "?C=M;O=D";
private static final long ONE_KB = 1024L;
@Autowired
ConfigService configService;
@Autowired
@Qualifier("local")
LogAccessService localLogAccessService;
@Override
public InputStream executeCommand(String logAccessConfigId, String shellCommand) throws LogAccessException {
// Get the LogAccessConfig
LogAccessConfig logAccessConfig = configService.getLogAccessConfig(logAccessConfigId);
// Generate authentication option (if requested in configuration)
String authenticationOption = "";
if (logAccessConfig.getUser() != null && logAccessConfig.getPassword() != null) {
authenticationOption = "-u \"" + logAccessConfig.getUser() + ":" + logAccessConfig.getPassword() + "\" ";
}
// Generate proxy option (if requested in configuration)
String proxyOption = "";
if (logAccessConfig.getProxy() != null) {
proxyOption = "-x " + logAccessConfig.getProxy() + " ";
}
// Generate curl command with full url
String urlPrefix = logAccessConfig.getUrl();
String fullUrlShellCommand = shellCommand.replaceFirst(Constants.HTTPD_FILE_VIEW_COMMAND_START, Constants.HTTPD_FILE_VIEW_COMMAND_START + authenticationOption + proxyOption + urlPrefix);
// Execute curl command
return localLogAccessService.executeCommand(logAccessConfigId, fullUrlShellCommand);
}
@Override
public void downloadFile(String logAccessConfigId, String fileName, OutputStream downloadOutputStream) throws LogAccessException {
// Get the LogAccessConfig
LogAccessConfig logAccessConfig = configService.getLogAccessConfig(logAccessConfigId);
// Define curl shell command
String shellCommand = Constants.HTTPD_FILE_VIEW_COMMAND_START + fileName;
// Execute the download
try {
InputStream remoteInputStream = executeCommand(logAccessConfigId, shellCommand);
FileCopyUtils.copy(remoteInputStream, downloadOutputStream);
}
catch (IOException e) {
throw new LogAccessException("Error when executing downloading " + fileName + " on " + logAccessConfig, e);
}
}
@Override
public Set<FileInfo> listFiles(String logAccessConfigId, String subPath) throws LogAccessException {
// Get the LogAccessConfig
LogAccessConfig logAccessConfig = configService.getLogAccessConfig(logAccessConfigId);
// Define curl shell command
String shellCommand = Constants.HTTPD_FILE_VIEW_COMMAND_START;
if (subPath != null) {
shellCommand += subPath + DIRECTORY_SEPARATOR;
}
shellCommand += LAST_MODIFIED_SORT;
// Execute command to get file list
BufferedReader remoteReader;
try {
InputStream remoteInputStream = executeCommand(logAccessConfigId, shellCommand);
remoteReader = new BufferedReader(new InputStreamReader(remoteInputStream, Constants.ISO_ENCODING));
}
catch (IOException e) {
throw new LogAccessException("Error when connecting to " + logAccessConfig, e);
}
String currentLine = null;
try {
// Read until table head
StringBuilder startContent = new StringBuilder();
boolean isTableStartReached = false;
while ( (currentLine = remoteReader.readLine()) != null) {
startContent.append(currentLine).append("\n");
if (currentLine.contains(TABLE_START)) {
isTableStartReached = true;
break;
}
}
if (!isTableStartReached) {
throw new LogAccessException("Impossible to get log files list on URL " + logAccessConfig.getUrl() + " :\n" + startContent.toString());
}
// Result meta-informations
Set<FileInfo> fileInfos = new TreeSet<FileInfo>();
int maxFileCount = configService.getFileListMaxCount();
int fileCount = 0;
// Extract files and directories : each line is a file/directory
while ( (currentLine = remoteReader.readLine()) != null) {
// Last Line
if (currentLine.contains(TABLE_END)) {
break;
}
// Check file count
++fileCount;
if (fileCount > maxFileCount) {
break;
}
// Parse file name
int linkHrefStart = currentLine.indexOf(LINK_HREF_START) + LINK_HREF_START.length();
int linkHrefEnd = currentLine.indexOf(LINK_HREF_END, linkHrefStart);
String fileName = currentLine.substring(linkHrefStart, linkHrefEnd);
boolean isDirectory = fileName.endsWith(DIRECTORY_SEPARATOR);
if (isDirectory) {
fileName = fileName.substring(0, fileName.length() - 1);
}
// Parse date
int linkTagEnd = currentLine.indexOf(LINK_END_TAG) + LINK_END_TAG.length();
String[] dateAndSize = currentLine.substring(linkTagEnd).trim().replaceAll("\\s+", " ").split(" ");
String dateAsString = dateAndSize[0] + " " + dateAndSize[1];
Date date;
if (dateAsString.length() == DATE_FORMAT_NUMERIC.length()) {
date = new SimpleDateFormat(DATE_FORMAT_NUMERIC).parse(dateAsString);
}
else {
date = new SimpleDateFormat(DATE_FORMAT_LITTERAL, Locale.US).parse(dateAsString);
}
// Parse size
long size = 0;
if (!isDirectory) {
String sizeAsString = dateAndSize[2];
if (sizeAsString.matches("[0-9.]+[KMG]")) {
BigDecimal sizeAsBigDecimal = new BigDecimal(sizeAsString.substring(0, sizeAsString.length()-1));
char sizeLastChar = sizeAsString.charAt(sizeAsString.length()-1);
switch (sizeLastChar) {
case 'K':
sizeAsBigDecimal = sizeAsBigDecimal.multiply(BigDecimal.valueOf(ONE_KB));
break;
case 'M':
sizeAsBigDecimal = sizeAsBigDecimal.multiply(BigDecimal.valueOf(ONE_KB * ONE_KB));
break;
case 'G':
sizeAsBigDecimal = sizeAsBigDecimal.multiply(BigDecimal.valueOf(ONE_KB * ONE_KB * ONE_KB));
break;
}
size = sizeAsBigDecimal.longValue();
}
else {
size = Long.parseLong(sizeAsString);
}
}
// Extract meta-informations
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(fileName);
fileInfo.setRelativePath((subPath != null ? subPath + DIRECTORY_SEPARATOR : "") + fileName);
fileInfo.setDirectory(isDirectory);
fileInfo.setLastModified(date);
fileInfo.setFileSize(size);
fileInfo.setLogAccessType(LogAccessType.HTTPD);
fileInfos.add(fileInfo);
}
// Return meta-informations about files and directories
return fileInfos;
}
catch (RuntimeException e) {
throw new LogAccessException("Error when parsing this line :\n" + currentLine, e);
}
catch (ParseException e) {
throw new LogAccessException("Error when parsing this line :\n" + currentLine, e);
}
catch (IOException e) {
throw new LogAccessException("I/O Error when parsing log files list\n" + e.getMessage(), e);
}
finally {
IOUtils.closeQuietly(remoteReader);
}
}
}