All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.duracloud.snapshot.service.impl.SnapshotManagerImpl Maven / Gradle / Ivy

/*
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 *     http://duracloud.org/license/
 */
package org.duracloud.snapshot.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.duracloud.client.ContentStore;
import org.duracloud.client.task.SnapshotTaskClient;
import org.duracloud.common.constant.Constants;
import org.duracloud.common.notification.NotificationManager;
import org.duracloud.common.notification.NotificationType;
import org.duracloud.common.util.ChecksumUtil;
import org.duracloud.common.util.ChecksumUtil.Algorithm;
import org.duracloud.common.util.IOUtil;
import org.duracloud.error.ContentStoreException;
import org.duracloud.error.NotFoundException;
import org.duracloud.snapshot.SnapshotException;
import org.duracloud.snapshot.SnapshotNotFoundException;
import org.duracloud.snapshot.common.SnapshotServiceConstants;
import org.duracloud.snapshot.db.ContentDirUtils;
import org.duracloud.snapshot.db.model.DuracloudEndPointConfig;
import org.duracloud.snapshot.db.model.Snapshot;
import org.duracloud.snapshot.db.model.SnapshotContentItem;
import org.duracloud.snapshot.db.model.SnapshotHistory;
import org.duracloud.snapshot.db.repo.SnapshotContentItemRepo;
import org.duracloud.snapshot.db.repo.SnapshotRepo;
import org.duracloud.snapshot.dto.SnapshotStatus;
import org.duracloud.snapshot.dto.task.CompleteSnapshotTaskResult;
import org.duracloud.snapshot.service.AlternateIdAlreadyExistsException;
import org.duracloud.snapshot.service.BridgeConfiguration;
import org.duracloud.snapshot.service.SnapshotManager;
import org.duracloud.snapshot.service.SnapshotManagerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;


/**
 * @author Daniel Bernstein Date: Jul 31, 2014
 */
@Component
public class SnapshotManagerImpl implements SnapshotManager {
    private static Logger log = LoggerFactory.getLogger(SnapshotManagerImpl.class);

    protected static String[] METADATA_FILENAMES = {Constants.SNAPSHOT_PROPS_FILENAME, 
        SnapshotServiceConstants.CONTENT_PROPERTIES_JSON_FILENAME, 
        SnapshotServiceConstants.MANIFEST_MD5_TXT_FILE_NAME,
        SnapshotServiceConstants.MANIFEST_SHA256_TXT_FILE_NAME};
    
    //NOTE: auto wiring at the field level rather than in the constructor seems to be necessary
    //      when annotating methods with @Transactional.
    @Autowired
    private SnapshotContentItemRepo snapshotContentItemRepo;

    @Autowired
    private SnapshotRepo snapshotRepo;

    @Autowired
    private NotificationManager notificationManager;

    @Autowired
    private SnapshotTaskClientHelper snapshotTaskClientHelper;
    
    @Autowired 
    private StoreClientHelper storeClientHelper;
    
    @Autowired
    private BridgeConfiguration bridgeConfig;

    public SnapshotManagerImpl() {}

    /**
     * @param snapshotContentItemRepo the snapshotContentItemRepo to set
     */
    public void setSnapshotContentItemRepo(SnapshotContentItemRepo snapshotContentItemRepo) {
        this.snapshotContentItemRepo = snapshotContentItemRepo;
    }

    /**
     * @param snapshotRepo the snapshotRepo to set
     */
    public void setSnapshotRepo(SnapshotRepo snapshotRepo) {
        this.snapshotRepo = snapshotRepo;
    }

    /**
     * @param notificationManager the notificationManager to set
     */
    public void setNotificationManager(NotificationManager notificationManager) {
        this.notificationManager = notificationManager;
    }

    /**
     * @param snapshotTaskClientHelper the snapshotTaskClientHelper to set
     */
    public void setSnapshotTaskClientHelper(SnapshotTaskClientHelper snapshotTaskClientHelper) {
        this.snapshotTaskClientHelper = snapshotTaskClientHelper;
    }

    /**
     * @param bridgeConfig the bridgeConfig to set
     */
    public void setBridgeConfig(BridgeConfiguration bridgeConfig) {
        this.bridgeConfig = bridgeConfig;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.duracloud.snapshot.service.SnapshotManager#addContentItem(
     *  org.duracloud.snapshot.db.model.Snapshot, java.lang.String, java.util.Map)
     */
    @Override
    @Transactional
    public void addContentItem(Snapshot snapshot,
                               String contentId,
                               Map props)
        throws SnapshotException {
        
        String contentIdHash = createChecksumGenerator().generateChecksum(contentId);
        try{
            
            if(this.snapshotContentItemRepo.findBySnapshotAndContentIdHash(snapshot, contentIdHash) != null){
                return;
            }
            
            SnapshotContentItem item = new SnapshotContentItem();
            item.setContentId(contentId);
            item.setSnapshot(snapshot);
            item.setContentIdHash(contentIdHash);
            String propString = PropertiesSerializer.serialize(props);
            item.setMetadata(propString);
            this.snapshotContentItemRepo.save(item);
        } catch(Exception ex){
            throw new SnapshotException("failed to add content item: " + ex.getMessage(), ex);
        }
    }

    @Override
    @Transactional
    public Snapshot addAlternateSnapshotIds(Snapshot snapshot, List alternateIds) throws AlternateIdAlreadyExistsException {
        snapshot = this.snapshotRepo.findOne(snapshot.getId());
        for(String altId : alternateIds){
            Snapshot altSnapshot = this.snapshotRepo.findBySnapshotAlternateIds(altId);
            if (altSnapshot != null && !altSnapshot.getName().equals(snapshot.getName())) {
                throw new AlternateIdAlreadyExistsException("The alternate snapshot id ("
                    + altId + ") already exists in another snapshot (" + altSnapshot.getName()+")");
            }
        }
        snapshot.addSnapshotAlternateIds(alternateIds);
        return this.snapshotRepo.saveAndFlush(snapshot);
    }

    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotManager#transferToDpnNodeComplete(java.lang.String)
     */
    @Override
    @Transactional
    public Snapshot transferToDpnNodeComplete(String snapshotId)
        throws SnapshotException {
        try {
            Snapshot snapshot = getSnapshot(snapshotId);

            snapshot.setStatus(SnapshotStatus.CLEANING_UP);
            snapshot.setStatusText("");
            snapshot = this.snapshotRepo.saveAndFlush(snapshot);

            File snapshotDir =
                new File(ContentDirUtils.getDestinationPath(snapshot.getName(),
                                                            BridgeConfiguration.getContentRootDir()));

             File zipFile = zipMetadata(snapshotId, snapshotDir);
            
            DuracloudEndPointConfig source = snapshot.getSource();

            ContentStore store = getContentStore(source);
            
            ensureMetadataSpaceExists(store); 
            
            String zipChecksum = createChecksumGenerator().generateChecksum(zipFile);

            try(FileInputStream zipStream = new FileInputStream(zipFile)) {
                store.addContent(Constants.SNAPSHOT_METADATA_SPACE,
                                 zipFile.getName(),
                                 zipStream,
                                 zipFile.length(),
                                 "application/zip",
                                 zipChecksum,
                                 null);
            }

            zipFile.delete();
            
            FileUtils.deleteDirectory(snapshotDir);

            String spaceId = source.getSpaceId();
            // Call DuraCloud to clean up snapshot
            getSnapshotTaskClient(source).cleanupSnapshot(spaceId);
            log.info("successfully initiated snapshot cleanup on DuraCloud for snapshotId = "
                + snapshotId + "; spaceId = " + spaceId);
            
            return snapshot;
        } catch (Exception e) {
            String message = "failed to initiate snapshot clean up: " + e.getMessage();
            log.error(message, e);
            throw new SnapshotManagerException(e.getMessage());
        }
    }

    /**
     * @return
     */
    private ChecksumUtil createChecksumGenerator() {
        return new ChecksumUtil(Algorithm.MD5);
    }

    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotManager#transferError(java.lang.String)
     */
    @Override
    @Transactional
    public Snapshot transferError(String snapshotId, String errorDetails)
        throws SnapshotException {
        try {
            Snapshot snapshot = getSnapshot(snapshotId);

            // Set snapshot state in the db
            snapshot.setStatus(SnapshotStatus.ERROR);
            snapshot.setStatusText(errorDetails);
            snapshot = this.snapshotRepo.saveAndFlush(snapshot);

            // Send email to duracloud administrators
            String subject = "Snapshot ERROR: " + snapshotId;
            String message = "A snapshot process has been halted and set to the " +
                             "error state.\n\nSnapshot ID: " + snapshotId +
                             "\nReported Error: " + errorDetails;
            String[] recipients = bridgeConfig.getDuracloudEmailAddresses();
            notificationManager.sendNotification(NotificationType.EMAIL,
                                                 subject,
                                                 message,
                                                 recipients);

            log.info("successfully set snapshot " + snapshotId +
                     " into error state based on the following error details: " +
                     errorDetails);

            return snapshot;
        } catch (Exception e) {
            String message = "failed to set snapshot into error state due to: " +
                             e.getMessage();
            log.error(message, e);
            throw new SnapshotManagerException(e.getMessage());
        }
    }

    /**
     * @param store
     */
    private void ensureMetadataSpaceExists(ContentStore store) throws ContentStoreException {
        String spaceId = Constants.SNAPSHOT_METADATA_SPACE;
        try {
            store.getSpace(spaceId, null, 0, null);
        } catch (NotFoundException e) {
            store.createSpace(spaceId);
        }
    }

    /**
     * @param snapshotId
     * @param snapshotDir
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     */
    private File zipMetadata(String snapshotId, File snapshotDir)
        throws FileNotFoundException,
            IOException {

        File zipFile = new File(snapshotDir, snapshotId+".zip");
        
        FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
        ZipOutputStream zipOs = new ZipOutputStream(fileOutputStream);
        
        for(String file : METADATA_FILENAMES) {
            IOUtil.addFileToZipOutputStream(new File(snapshotDir, file), zipOs);
        }

        zipOs.close();
        return zipFile;
    }

    /**
     * @param source
     * @return
     */
    private ContentStore getContentStore(DuracloudEndPointConfig source) {
        ContentStore store =
            storeClientHelper.create(source,
                                     bridgeConfig.getDuracloudUsername(),
                                     bridgeConfig.getDuracloudPassword());
        return store;
    }


    /**
     * Build the snapshot task client - for communicating with the DuraCloud snapshot
     * provider to perform tasks.
     *
     * @param source DuraCloud connection source
     * @return task client
     */
    private SnapshotTaskClient getSnapshotTaskClient(DuracloudEndPointConfig source) {
        return this.snapshotTaskClientHelper.create(source,
                                                    bridgeConfig.getDuracloudUsername(),
                                                    bridgeConfig.getDuracloudPassword());
    }

    /**
     * @param snapshotId
     * @return
     */
    private Snapshot getSnapshot(String snapshotId) throws SnapshotException {
        Snapshot snapshot = this.snapshotRepo.findByName(snapshotId);
        if (snapshot == null) {
            throw new SnapshotNotFoundException("A snapshot with id "
                + snapshotId + " does not exist.");
        }
        return snapshot;
    }

    private Snapshot cleanupComplete(Snapshot snapshot)
        throws SnapshotException {
        snapshot.setEndDate(new Date());
        snapshot.setStatus(SnapshotStatus.SNAPSHOT_COMPLETE);
        snapshot.setStatusText("");
        snapshot = snapshotRepo.saveAndFlush(snapshot);
        String snapshotId = snapshot.getName();
        String message = "Snapshot complete: " + snapshotId;
        List recipients =
            new ArrayList<>(Arrays.asList(this.bridgeConfig.getDuracloudEmailAddresses()));
        String userEmail = snapshot.getUserEmail();
        if (userEmail != null) {
            recipients.add(userEmail);
        }

        if (recipients.size() > 0) {
            this.notificationManager.sendNotification(NotificationType.EMAIL,
                                                      message,
                                                      message,
                                                      recipients.toArray(new String[0]));
        }
        
        return snapshot;
    }
    
    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotManager#finalizeSnapshots()
     */
    @Override
    @Transactional
    public void finalizeSnapshots() {
         log.debug("Running finalize snapshots...");
        List snapshots =
            this.snapshotRepo.findByStatus(SnapshotStatus.CLEANING_UP);
        for(Snapshot snapshot : snapshots){
            DuracloudEndPointConfig source = snapshot.getSource();
            ContentStore store = getContentStore(source);
            try {
                String spaceId = source.getSpaceId();
                Iterator it  = store.getSpaceContents(spaceId);
                if(!it.hasNext()) {
                    // Call DuraCloud to complete snapshot
                    log.debug("notifying task provider that snapshot " +
                              "is complete for space " + spaceId );
                    CompleteSnapshotTaskResult result =
                        getSnapshotTaskClient(source).completeSnapshot(spaceId);
                    log.info("snapshot complete call to task provider performed " +
                             "for space " + spaceId + ": result = " + result.getResult());

                    //update snapshot status and notify users
                    cleanupComplete(snapshot);
                }
            } catch (Exception e) {
                log.error("failed to cleanup " + source);
            }
        }
    }

    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotManager#updateHistory()
     */
    @Override
    @Transactional
    public Snapshot updateHistory(Snapshot snapshot, String history) {
        snapshot = this.snapshotRepo.getOne(snapshot.getId());
        SnapshotHistory newHistory = new SnapshotHistory();
        newHistory.setHistory(history);
        newHistory.setSnapshot(snapshot);
        snapshot.getSnapshotHistory().add(newHistory);
        return this.snapshotRepo.save(snapshot);
    }

    /**
     * @param storeClientHelper the storeClientHelper to set
     */
    public void setStoreClientHelper(StoreClientHelper storeClientHelper) {
        this.storeClientHelper = storeClientHelper;
    }
    
    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotManager#deleteSnapshot(java.lang.String)
     */
    @Override
    @Transactional
    public void deleteSnapshot(String snapshotId) {
        //delete the snapshot
        snapshotContentItemRepo.deleteBySnapshotName(snapshotId);
        snapshotRepo.deleteByName(snapshotId);
        log.info("successfully deleted snapshot: {}", snapshotId);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy