001package com.credibledoc.substitution.doc.module.substitution.file;
002
003import com.credibledoc.combiner.application.Application;
004import com.credibledoc.combiner.application.ApplicationService;
005import com.credibledoc.combiner.file.FileService;
006import com.credibledoc.combiner.node.applicationlog.ApplicationLog;
007import com.credibledoc.combiner.node.file.NodeFileService;
008import com.credibledoc.substitution.core.exception.SubstitutionRuntimeException;
009import com.google.common.base.Preconditions;
010import lombok.RequiredArgsConstructor;
011import org.apache.commons.compress.archivers.ArchiveInputStream;
012import org.apache.commons.compress.archivers.ArchiveStreamFactory;
013import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
014import org.apache.commons.compress.utils.IOUtils;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017import org.springframework.stereotype.Service;
018
019import javax.inject.Inject;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileOutputStream;
023import java.io.InputStream;
024import java.util.*;
025import java.util.Map.Entry;
026
027@Service
028@RequiredArgsConstructor(onConstructor = @__(@Inject))
029public class FileUtilService {
030
031    private static final Logger logger = LoggerFactory.getLogger(FileUtilService.class);
032
033    private static final char DOT = '.';
034
035    private static final String REPORT_FOLDER_EXTENSION = ".report";
036
037    /**
038     * Generate a new file with transformed content of source log files.
039     * @param fileNumber an order number. Can be 'null' if a single file
040     *                   should be created in the directory. If not 'null',
041     *                   this number will be appended to the file name,
042     *                   for example index_001.html
043     * @param fileName the file name without extension
044     * @param reportDirectory where the new file will be created
045     * @param fileExtension for example html or txt
046     *
047     * @return the new file with context transformed from the source log file.
048     */
049    public File generateFile(Integer fileNumber, String fileName, File reportDirectory, String fileExtension) {
050        String num = null;
051        if (fileNumber != null) {
052            num = String.format("%03d", fileNumber);
053        }
054        StringBuilder newFileName = new StringBuilder(fileName);
055        if (num != null) {
056            newFileName.append(DOT).append(num);
057        }
058        newFileName.append(DOT).append(fileExtension);
059        File file = new File(reportDirectory, newFileName.toString());
060        logger.info("The new empty file created: '{}'", file.getAbsolutePath());
061        return file;
062    }
063
064    /**
065     * Sort files in a directory from the first argument.
066     * For each {@link Application} creates its own list of files.
067     *
068     * @param directory       cannot be 'null'. Can have files from different {@link Application}s.<br>
069     *                        Cannot contain other files. But can have directories. These directories
070     *                        will be processed recursively.
071     * @param applicationLogs at first invocation an empty, and it will be filled with files
072     */
073    private void collectApplicationLogs(File directory, List<ApplicationLog> applicationLogs) {
074        Preconditions.checkNotNull(directory);
075        Preconditions.checkState(directory.isDirectory());
076        Map<Application, Map<Date, File>> map = new HashMap<>();
077        File[] files = Objects.requireNonNull(directory.listFiles());
078        for (File file : files) {
079            addFileToMap(applicationLogs, map, files, file);
080        }
081        ApplicationService applicationService = ApplicationService.getInstance();
082        for (Entry<Application, Map<Date, File>> appEntry : map.entrySet()) {
083            Application application = appEntry.getKey();
084            ApplicationLog applicationLog = applicationService.findOrCreate(applicationLogs, application);
085            NodeFileService.getInstance().appendToNodeLogs(appEntry.getValue(), applicationLog);
086        }
087    }
088
089    private void addFileToMap(List<ApplicationLog> applicationLogs, Map<Application,
090            Map<Date, File>> map, File[] files, File file) {
091
092        if (file.isFile()) {
093            if (file.getName().endsWith(".zip")) {
094                file = unzipIfNotExists(file, files);
095            }
096            Application application = FileService.getInstance().findApplication(file);
097            if (!map.containsKey(application)) {
098                map.put(application, new TreeMap<>());
099            }
100
101            Date date = FileService.getInstance().findDate(file, application);
102            if (date == null) {
103                throw new SubstitutionRuntimeException("Cannot find a date in the file: " + file.getAbsolutePath());
104            }
105            map.get(application).put(date, file);
106        } else {
107            // directories
108            if (!file.getName().endsWith(REPORT_FOLDER_EXTENSION)) {
109                collectApplicationLogs(file, applicationLogs);
110            }
111        }
112    }
113
114    /**
115     * If the second argument contains unzipped first argument, do not unzip it.
116     * Else unzip it and return a file from this zipFile.
117     * @param zipFile zipped log file
118     * @param files all files in a directory
119     * @return an unzipped file or file from files
120     */
121    private File unzipIfNotExists(File zipFile, File[] files) {
122        try (
123            InputStream is = new FileInputStream(zipFile);
124            ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream("zip", is)
125        ) {
126            ZipArchiveEntry zipArchiveEntry = (ZipArchiveEntry) ais.getNextEntry();
127            String zipArchiveEntryName = zipArchiveEntry.getName();
128            for (File file : files) {
129                if (file.getName().equals(zipArchiveEntryName)) {
130                    return file;
131                }
132            }
133            File outputFile = new File(zipFile.getParentFile(), zipArchiveEntryName);
134            logger.info("File {}:{} will be decompressed to {}", zipFile.getName(), zipArchiveEntryName, outputFile.getAbsolutePath());
135            IOUtils.copy(ais, new FileOutputStream(outputFile));
136            return outputFile;
137        } catch (Exception e) {
138            throw new SubstitutionRuntimeException("Cannot unzipIfNotExists file: " + zipFile.getAbsolutePath(), e);
139        }
140    }
141
142}
143