org.sigmah.server.file.impl.BackupArchiveManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.server.file.impl.BackupArchiveManagerImpl.java

Source

package org.sigmah.server.file.impl;

/*
 * #%L
 * Sigmah
 * %%
 * Copyright (C) 2010 - 2016 URD
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.ArrayUtils;
import org.sigmah.server.conf.Properties;
import org.sigmah.server.dao.OrgUnitDAO;
import org.sigmah.server.domain.User;
import org.sigmah.server.file.BackupArchiveJobFactory;
import org.sigmah.server.file.BackupArchiveManager;
import org.sigmah.server.file.impl.BackupArchiveJob.BackupArchiveJobArgument;
import org.sigmah.shared.conf.PropertyKey;
import org.sigmah.shared.dto.BackupDTO;
import org.sigmah.shared.dto.value.FileDTO.LoadingScope;
import org.sigmah.shared.util.FileType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import java.util.Arrays;
import java.util.Comparator;
import org.sigmah.shared.dispatch.FunctionalException;

/**
 * {@link BackupArchiveManager} implementation.
 * 
 * @author Denis Colliot (dcolliot@ideia.fr)
 */
public class BackupArchiveManagerImpl implements BackupArchiveManager {

    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(BackupArchiveManagerImpl.class);

    /**
     * Separator used in backup archive files name.
     */
    private static final String BACKUP_ARCHIVE_NAME_SEP = "-";

    /**
     * Archive files extension (with separator).
     */
    private static final String BACKUP_ARCHIVE_EXT = FileType.ZIP.getExtension();

    /**
     * Temporary files extension (with separator).
     */
    private static final String BACKUP_ARCHIVE_TEMP_EXT = ".tmp";

    /**
     * <p>
     * Backup archive file name pattern.
     * </p>
     * <p>
     * Detects following groups:
     * </p>
     * 
     * <pre>
     * {organizationId}_{orgUnitId}_{loadingScope}.{extension}
     * </pre>
     */
    private static final Pattern BACKUP_ARCHIVE_NAME_PATTERN = Pattern.compile("(\\d*)" + BACKUP_ARCHIVE_NAME_SEP
            + "(\\d*)" + BACKUP_ARCHIVE_NAME_SEP + "(" + LoadingScope.ALL_VERSIONS + '|' + LoadingScope.LAST_VERSION
            + ")" + "(" + BACKUP_ARCHIVE_EXT + '|' + BACKUP_ARCHIVE_TEMP_EXT + ")");

    /**
     * Injected application properties.
     */
    private final Properties properties;

    /**
     * Injected {@link OrgUnitDAO}.
     */
    private final OrgUnitDAO orgUnitDAO;

    /**
     * Injected {@link BackupArchiveJobFactory}.
     */
    private final BackupArchiveJobFactory backupArchiveJobFactory;

    @Inject
    public BackupArchiveManagerImpl(final Properties properties, final OrgUnitDAO orgUnitDAO,
            final BackupArchiveJobFactory backupArchiveJobFactory) {
        this.properties = properties;
        this.orgUnitDAO = orgUnitDAO;
        this.backupArchiveJobFactory = backupArchiveJobFactory;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InputStream open(final String archiveId) throws IOException {

        if (LOG.isTraceEnabled()) {
            LOG.trace("Opening a new stream to archive file '{}'.", archiveId);
        }

        return Files.newInputStream(Paths.get(getArchiveRootPath(), archiveId));

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BackupDTO getRunningBackupProcessFile(final Integer organizationId) throws IOException {

        if (LOG.isTraceEnabled()) {
            LOG.trace("Looking for running backup process file for organization #{}.", organizationId);
        }

        final File[] tempFiles = Paths.get(getArchiveRootPath()).toFile().listFiles(new FilenameFilter() {

            @Override
            public boolean accept(final File file, final String name) {
                return name.startsWith(organizationId + BACKUP_ARCHIVE_NAME_SEP)
                        && name.endsWith(BACKUP_ARCHIVE_TEMP_EXT);
            }
        });

        if (ArrayUtils.isEmpty(tempFiles)) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("No running backup process file has been found for organization #{}.", organizationId);
            }
            return null;
        }

        return fromFile(tempFiles[0].toPath());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BackupDTO getExistingBackup(final Integer organizationId) throws IOException {

        if (LOG.isTraceEnabled()) {
            LOG.trace("Looking for existing backup file for organization #{}.", organizationId);
        }

        final File[] archiveFiles = Paths.get(getArchiveRootPath()).toFile().listFiles(new FilenameFilter() {

            @Override
            public boolean accept(final File file, final String name) {
                return name.startsWith(organizationId + BACKUP_ARCHIVE_NAME_SEP)
                        && name.endsWith(BACKUP_ARCHIVE_EXT);
            }
        });

        if (ArrayUtils.isEmpty(archiveFiles)) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("No existing backup file has been found for organization #{}.", organizationId);
            }
            return null;
        }

        // BUGFIX #671 & #772: Sorting files by creation date to retrieve the most recent backup.
        Arrays.sort(archiveFiles, new Comparator<File>() {

            @Override
            public int compare(File o1, File o2) {
                return getCreationDate(o2).compareTo(getCreationDate(o1));
            }

        });

        return fromFile(archiveFiles[0].toPath());
    }

    /**
     * Returns the creation date of the given file or the 1st january 1970
     * if an error occured while trying to read the date.
     * 
     * @param file File to access.
     * @return Creation date of the given file.
     */
    private Date getCreationDate(File file) {
        try {
            final BasicFileAttributeView view = Files.getFileAttributeView(file.toPath(),
                    BasicFileAttributeView.class);
            final BasicFileAttributes attributes = view.readAttributes();

            return new Date(attributes.creationTime().toMillis());

        } catch (IOException e) {
            return new Date(0L);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void startBackupArchiveGeneration(final BackupDTO backup, final User user)
            throws IOException, FunctionalException {

        if (LOG.isTraceEnabled()) {
            LOG.trace("Starting new backup process for configuration '{}' ; Backup launched by user '{}'.", backup,
                    user);
        }

        if (backup == null || backup.getOrganizationId() == null || backup.getOrgUnitId() == null) {
            throw new IllegalArgumentException("Backup configuration is invalid.");
        }

        // Archive files.
        final Path tempArchiveFile = Paths.get(getArchiveRootPath(), buildArchiveFileName(backup, true));
        final Path finalArchiveFile = Paths.get(getArchiveRootPath(), buildArchiveFileName(backup, false));

        // Creates temporary file.
        try {
            Files.createFile(tempArchiveFile);

        } catch (IOException e) {
            throw new FunctionalException(e, FunctionalException.ErrorCode.ADMIN_BACKUP_ARCHIVE_CREATION_FAILED,
                    tempArchiveFile.toString());
        }

        if (LOG.isTraceEnabled()) {
            LOG.trace("Backup process file has been created: '{}'. Initializing job.", tempArchiveFile);
        }

        // Process is executed in a different thread in order to release current thread.
        Executors.newSingleThreadExecutor().execute(backupArchiveJobFactory
                .newJob(new BackupArchiveJobArgument(backup, user.getId(), tempArchiveFile, finalArchiveFile)));
    }

    // --------------------------------------------------------------------------------------------------------------
    //
    // UTILITY METHODS.
    //
    // --------------------------------------------------------------------------------------------------------------

    /**
     * Returns the archives storage root directory path.
     * 
     * @return The archives storage root directory path.
     */
    private String getArchiveRootPath() {
        return properties.getProperty(PropertyKey.ARCHIVE_REPOSITORY_NAME);
    }

    /**
     * <p>
     * Builds the given {@code backup} corresponding archive file name.
     * </p>
     * <p>
     * Archive file name is generated with following format: {@code <organizationId>_<orgUnitId>_<loadingMode>.zip}
     * </p>
     * 
     * @param backup
     *          The backup configuration.
     * @param tempFile
     *          {@code true} to generate a temporary file name, {@code false} to generate a complete file name.
     * @return The {@code backup} corresponding archive file name (with extension).
     */
    private static String buildArchiveFileName(final BackupDTO backup, final boolean tempFile) {

        final StringBuilder builder = new StringBuilder();

        builder.append(backup.getOrganizationId());
        builder.append(BACKUP_ARCHIVE_NAME_SEP);
        builder.append(backup.getOrgUnitId());
        builder.append(BACKUP_ARCHIVE_NAME_SEP);
        builder.append(backup.getLoadingScope().name());
        builder.append(tempFile ? BACKUP_ARCHIVE_TEMP_EXT : BACKUP_ARCHIVE_EXT);

        return builder.toString();
    }

    /**
     * Builds the given {@code file} corresponding {@link BackupDTO}.
     * 
     * @param file
     *          The backup file path.
     * @return The given {@code file} corresponding {@link BackupDTO}.
     * @throws IOException
     *           If an I/O error occurs.
     * @throws IllegalArgumentException
     *           If the given {@code file} does not reference a valid backup file.
     */
    private BackupDTO fromFile(final Path file) throws IOException {

        final String filename = file.getFileName().toString();
        final Matcher matcher = BACKUP_ARCHIVE_NAME_PATTERN.matcher(filename);

        if (!matcher.matches()) {
            throw new IllegalArgumentException("Invalid backup archive file name '" + filename + "'.");
        }

        final Integer organizationId = Integer.parseInt(matcher.group(1));
        final Integer orgUnitId = Integer.parseInt(matcher.group(2));
        final LoadingScope loadingScope = LoadingScope.valueOf(matcher.group(3));
        final String extension = matcher.group(4);

        final BasicFileAttributeView view = Files.getFileAttributeView(file, BasicFileAttributeView.class);
        final BasicFileAttributes attributes = view.readAttributes();

        final BackupDTO result = new BackupDTO();

        result.setOrganizationId(organizationId);
        result.setOrgUnitId(orgUnitId);
        result.setOrgUnitName(orgUnitDAO.findById(orgUnitId).getFullName());
        result.setLoadingScope(loadingScope);
        result.setCreationDate(new Date(attributes.creationTime().toMillis()));
        result.setArchiveFileName(filename);
        result.setRunning(extension.equals(BACKUP_ARCHIVE_TEMP_EXT));

        return result;
    }
}