com.mgmtp.jfunk.core.scripting.ModuleArchiver.java Source code

Java tutorial

Introduction

Here is the source code for com.mgmtp.jfunk.core.scripting.ModuleArchiver.java

Source

/*
 * Copyright (c) 2015 mgm technology partners GmbH
 *
 * Licensed 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.
 */
package com.mgmtp.jfunk.core.scripting;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.io.FileUtils.copyDirectory;
import static org.apache.commons.io.FileUtils.copyFileToDirectory;
import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.apache.commons.io.IOUtils.closeQuietly;
import static org.apache.commons.io.IOUtils.copy;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.text.Format;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;

import com.google.common.io.Files;
import com.mgmtp.jfunk.common.JFunkConstants;
import com.mgmtp.jfunk.common.config.ModuleScoped;
import com.mgmtp.jfunk.common.exception.JFunkException;
import com.mgmtp.jfunk.common.random.MathRandom;
import com.mgmtp.jfunk.common.util.Configuration;
import com.mgmtp.jfunk.common.util.ExtendedProperties;
import com.mgmtp.jfunk.core.config.ArchiveDir;
import com.mgmtp.jfunk.core.module.TestModule;
import com.mgmtp.jfunk.data.DataSet;
import com.mgmtp.jfunk.data.source.DataSource;

/**
 * Handles archiving of {@link TestModule}s. There are three different archiving modes:
 * <ul>
 * <li>{@link JFunkConstants#ARCHIVING_MODE_ALL ALL} - archiving happens always, i. e an archive
 * directory is created and zipped up after a {@link TestModule} has finished</li>
 * <li>{@link JFunkConstants#ARCHIVING_MODE_ERROR ERROR} - archiving happens during the execution of
 * a {@link TestModule}, but the archive is only zipped up when an eror occurs and otherwise deleted
 * </li>
 * <li>{@link JFunkConstants#ARCHIVING_MODE_NONE NONE} - no archiving happens at all</li>
 * </ul>
 * 
 * @author rnaegele
 */
@ModuleScoped
public class ModuleArchiver {
    private static final Format FORMAT = FastDateFormat.getInstance("yyyyMMdd_HHmmss", Locale.GERMANY);
    private static final String DIR_PATTERN = "%s_%s_[%s]";

    private final Logger log = LoggerFactory.getLogger(getClass());

    private final Configuration configuration;
    private final DataSource dataSource;

    private final File archiveDir;
    private final Charset charset;

    private File moduleArchiveDir;

    private FileAppender<ILoggingEvent> moduleAppender;
    private final MathRandom random;
    private final ArchivingMode archivingMode;
    private final ModuleMetaData moduleMetaData;

    /**
     * Creates a new instance.
     * 
     * @param configuration
     *            the configuration instance
     * @param dataSource
     *            the dats source
     * @param archiveDir
     *            the archive directory
     */
    @Inject
    ModuleArchiver(final Configuration configuration, final DataSource dataSource,
            @ArchiveDir final File archiveDir, final Charset charset, final MathRandom random,
            final ArchivingMode archivingMode, final ModuleMetaData moduleMetaData) {
        this.configuration = configuration;
        this.dataSource = dataSource;
        this.archiveDir = archiveDir;
        this.charset = charset;
        this.random = random;
        this.archivingMode = archivingMode;
        this.moduleMetaData = moduleMetaData;
    }

    /**
     * Starts archiving of the specified module, if archiving is enabled.
     */
    void startArchiving() {
        if (isArchivingDisabled()) {
            return;
        }

        String archiveName = configuration.get(JFunkConstants.ARCHIVE_FILE);
        if (StringUtils.isBlank(archiveName)) {
            archiveName = String.format(DIR_PATTERN, moduleMetaData.getModuleName(),
                    Thread.currentThread().getName(), FORMAT.format(moduleMetaData.getStartDate()));
        }

        moduleArchiveDir = new File(archiveDir, archiveName);
        if (moduleArchiveDir.exists()) {
            log.warn("Archive directory already exists: {}", moduleArchiveDir);
        } else {
            moduleArchiveDir.mkdirs();
        }

        addModuleAppender();
        log.info("Started archiving: (module={}, moduleArchiveDir={})", moduleMetaData.getModuleName(),
                moduleArchiveDir);
    }

    private void addModuleAppender() {
        final Thread thread = Thread.currentThread();

        moduleAppender = new FileAppender<ILoggingEvent>() {
            @Override
            protected void subAppend(final ILoggingEvent event) {
                if (thread.equals(Thread.currentThread())) {
                    super.subAppend(event);
                }
            }
        };

        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        moduleAppender.setContext(loggerContext);
        moduleAppender.setFile(new File(moduleArchiveDir, "module.log").getPath());

        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{35} - %msg%n");
        encoder.start();

        moduleAppender.setEncoder(encoder);
        moduleAppender.start();

        // attach the rolling file appender to the logger of your choice
        ch.qos.logback.classic.Logger logbackLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
        logbackLogger.addAppender(moduleAppender);
    }

    void finishArchiving() {
        if (archivingMode == ArchivingMode.none) {
            return;
        }

        boolean success = !moduleMetaData.isError();
        try {
            if (archivingMode == ArchivingMode.error && success) {
                return;
            }

            Configuration configClone = configuration.clone();
            configClone.put(JFunkConstants.CURRENT_MODULE_NAME, moduleMetaData.getModuleName());
            configClone.put(JFunkConstants.CURRENT_MODULE_RESULT,
                    success ? JFunkConstants.OK : JFunkConstants.ERROR);
            configClone.put(JFunkConstants.TESTMODULE_CLASS, moduleMetaData.getModuleClass().getName());
            configClone.put(JFunkConstants.RANDOM_SEED, String.valueOf(random.getSeed()));

            saveDataSets(configClone);
            saveConfiguration(configClone);
            saveStackTrace(moduleMetaData.getThrowable());

            log.info("Finished archiving: (module={}, moduleArchiveDir={})", moduleMetaData.getModuleName(),
                    moduleArchiveDir);
        } finally {
            if (moduleAppender != null) {
                LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
                moduleAppender.stop();
                loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).detachAppender(moduleAppender);
            }

            if (archivingMode == ArchivingMode.error && success) {
                deleteQuietly(moduleArchiveDir);
            } else {
                String archiveType = configuration.get(JFunkConstants.ARCHIVE_TYPE, "zip");
                if ("dir".equals(archiveType)) {
                    File dest = new File(moduleArchiveDir.getParent(),
                            moduleArchiveDir.getName() + (success ? "_ok" : "_error"));
                    moduleArchiveDir.renameTo(dest);
                } else {
                    zipUpArchive(success);
                    deleteQuietly(moduleArchiveDir);
                }
            }
        }
    }

    /**
     * Gets the module's archive directory.
     * 
     * @return the moduleArchiveDir
     */
    public File getModuleArchiveDir() {
        return moduleArchiveDir;
    }

    /**
     * Adds a file to the archive directory if it is not already present in the archive directory.
     * 
     * @param fileOrDirToAdd
     *            the file or directory to add
     * @throws IOException
     *             if an IO error occurs during copying
     */
    public void addToArchive(final File fileOrDirToAdd) throws IOException {
        addToArchive(new File("."), fileOrDirToAdd);
    }

    /**
     * Adds a file or directory (recursively) to the archive directory if it is not already present
     * in the archive directory.
     * 
     * @param relativeDir
     *            the directory relative to the archive root directory which the specified file or
     *            directory is added to
     * @param fileOrDirToAdd
     *            the file or directory to add
     * @throws IOException
     *             if an IO error occurs during copying
     */
    public void addToArchive(final File relativeDir, final File fileOrDirToAdd) throws IOException {
        checkArgument(!relativeDir.isAbsolute(), "'relativeDir' must be a relative directory");

        if (isArchivingDisabled()) {
            return;
        }

        if (fileOrDirToAdd.getCanonicalPath().startsWith(moduleArchiveDir.getCanonicalPath())) {
            // File already in archive dir
            log.info("File '" + fileOrDirToAdd + "' already in archive directory. No copying necessary.");
            return;
        }

        File archiveTargetDir = new File(moduleArchiveDir, relativeDir.getPath());
        if (fileOrDirToAdd.isDirectory()) {
            copyDirectory(fileOrDirToAdd, archiveTargetDir);
        } else {
            copyFileToDirectory(fileOrDirToAdd, archiveTargetDir);
        }
    }

    private void saveConfiguration(final Configuration config) {
        /*
         * If the execution mode is set to "start" it will be set to "finish" so that the archived
         * run will be continued upon the next execution.
         */
        if (JFunkConstants.EXECUTION_MODE_START.equals(config.get(JFunkConstants.EXECUTION_MODE))) {
            config.put(JFunkConstants.EXECUTION_MODE, JFunkConstants.EXECUTION_MODE_FINISH);
        }

        File f = new File(moduleArchiveDir, JFunkConstants.SCRIPT_PROPERTIES);
        Writer out = null;
        try {
            out = Files.newWriter(f, charset);
            config.store(out, "Archived testing properties", true, false);
        } catch (IOException e) {
            throw new JFunkException("Could not write testing properties " + f.getName(), e);
        } finally {
            closeQuietly(out);
        }
    }

    public boolean isArchivingDisabled() {
        return archivingMode == ArchivingMode.none;
    }

    private void saveDataSets(final Configuration config) {
        if (config.getBoolean(JFunkConstants.ARCHIVE_DATASETS, true)) {
            File dir = new File(moduleArchiveDir, "formdata");
            checkState(dir.mkdir(), "Could not create directory " + dir.getPath() + " in archive dir " + this);

            for (Entry<String, DataSet> entry : dataSource.getCurrentDataSets().entrySet()) {
                DataSet data = entry.getValue();
                // May be null, if configured in script.properties but no data
                // available for the key.
                if (data != null) {
                    File f = new File(dir, entry.getKey() + JFunkConstants.FORM_PROPERTIES_ENDING);
                    config.put(JFunkConstants.FORM_DATA_PREFIX + entry.getKey() + JFunkConstants.PROPERTIES_ENDING,
                            "formdata/" + f.getName());
                    FileOutputStream out = null;
                    try {
                        out = new FileOutputStream(f);
                        ExtendedProperties p = new ExtendedProperties(data.getDataView());
                        p.store(out, "Archived form data", true, false);
                    } catch (IOException ex) {
                        throw new JFunkException("Could not write FormData properties " + f.getName(), ex);
                    } finally {
                        closeQuietly(out);
                    }
                }
            }
        }
    }

    private void saveStackTrace(final Throwable throwable) {
        if (throwable != null) {
            PrintWriter pr = null;
            try {
                pr = new PrintWriter(new File(moduleArchiveDir, JFunkConstants.STACKTRACE_LOG), "UTF-8");
                throwable.printStackTrace(pr);
            } catch (IOException ex) {
                throw new JFunkException("Error writing stacktrace log to archive.", ex);
            } finally {
                closeQuietly(pr);
            }
        }
    }

    private void zipUpArchive(final boolean success) {
        File[] files = moduleArchiveDir.listFiles();
        if (files.length == 0) {
            return;
        }

        File zipFile = new File(moduleArchiveDir.getParentFile(),
                moduleArchiveDir.getName() + (success ? "_ok.zip" : "_error.zip"));
        log.info("Creating zip file: {}", zipFile);

        ZipOutputStream zipOut = null;
        try {
            zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
            for (File file : files) {
                zip("", file, zipOut);
            }
        } catch (IOException ex) {
            throw new JFunkException("Error creating archive zip: " + zipFile, ex);
        } finally {
            closeQuietly(zipOut);
        }
    }

    private void zip(final String prefix, final File file, final ZipOutputStream zipOut) throws IOException {
        if (file.isDirectory()) {
            String recursePrefix = prefix + file.getName() + '/';
            for (File child : file.listFiles()) {
                zip(recursePrefix, child, zipOut);
            }
        } else {
            FileInputStream in = null;
            try {
                in = new FileInputStream(file);
                zipOut.putNextEntry(new ZipEntry(prefix + file.getName()));
                copy(in, zipOut);
            } finally {
                closeQuietly(in);
                zipOut.flush();
                zipOut.closeEntry();
            }
        }
    }

    public static enum ArchivingMode {
        all, error, none;
    }
}