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

com.sleepycat.je.BackupFSArchiveCopy Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.Properties;

import com.sleepycat.je.dbi.BackupManager;

/**
 * @hidden For internal use: automatic backups
 *
 * An implementation for copying files into a regular file system archive. The
 * archive would typically be hosted on a different disk from that used to host
 * the environment log files, or an nfs mounted file system.
 *
 * The current implementation is primarily intended for testing and to help
 * proof the interface. It does not support compression or encryption and is
 * not tuned for performance.
 */
public class BackupFSArchiveCopy implements BackupFileCopy {

    public static final String CHECKSUM_KEY = "checksumAlg";

    /*
     * Do page 4K aligned reads for good performance, 16 pages at a time
     */
    private static final int TRANSFER_BYTES = 0x1000 * 16;

    private volatile Properties initProperties;

    /* Configured properties, parsed from initProperties. */
    private volatile String encryptionAlg;

    private volatile String compressionAlg;

    private volatile String checksumAlg;

    /* No-args constructor required by implementation. */
    public BackupFSArchiveCopy() {
        super();
    }

    @Override
    public synchronized void initialize(File configFile)
        throws InterruptedException, IOException {

        if (initProperties != null) {
            throw new IllegalStateException("Already initialized");
        }

        Objects.requireNonNull(configFile, "configFile arg must not be null");

        final Properties props = new Properties();
        try (InputStream configStream = new FileInputStream(configFile)) {
           props.load(configStream);
        }
        checksumAlg = (String) props.remove(CHECKSUM_KEY);
        if (checksumAlg == null) {
            throw new IllegalArgumentException("checksum alg missing from:" +
                                                props);
        }

        try {
            /* Validate the algorithm. */
            MessageDigest.getInstance(checksumAlg);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Bad checksum algorithm", e);
        }

        if (props.size() > 0) {
            throw new IllegalArgumentException("Unknown properties:" +
                                               props);
        }
        /* Successful initialization. */
        initProperties = props;
    }

    @Override
    public String getEncryptionAlg() {
        requireInitialized();
        return encryptionAlg;
    }

    @Override
    public String getCompressionAlg() {
        requireInitialized();
        return compressionAlg;
    }

    @Override
    public String getChecksumAlg() {
        requireInitialized();
        return checksumAlg;
    }

    /**
     * Note that the copy implementation can afford to be simple and not clean
     * up on failure since a partially copied archiveURL file can never be part
     * of the manifest. Such a partially copied file will typically be
     * overwritten as the backup manager retries the copy and if it succeeds, it
     * becomes part of the manifest. If the copy is abandoned, the archive
     * pruning utility will eventually delete it and reclaim its storage.
     */
    @Override
    public byte[] copy(File localFile, URL archiveURL)
        throws InterruptedException, IOException {

        requireInitialized();
        Objects.requireNonNull(localFile, "localFile arg must not be null");
        Objects.requireNonNull(archiveURL, "archiveFile arg must not be null");

        final MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance(checksumAlg);
        } catch (NoSuchAlgorithmException e) {
            /* Should not happen, already checked during initialization. */
            throw new IllegalArgumentException("Bad checksum algorithm", e);
        }
        long length = localFile.length();

        final File archiveFile = getFileFromURL(archiveURL);
        try (final FileInputStream inputStream = new FileInputStream(localFile);
             final FileOutputStream outputStream =
                new FileOutputStream(archiveFile, false)) {
            final ByteBuffer buffer = ByteBuffer.allocate(TRANSFER_BYTES);
            for (long bytes = length; bytes > 0;) {
                int readSize = (int)Math.min(TRANSFER_BYTES, bytes);
                int readBytes = inputStream.read(buffer.array(), 0, readSize);
                if (readBytes == -1) {
                    throw new IOException("Premature EOF. Was expecting: " +
                                          readSize);
                }
                outputStream.write(buffer.array(), 0, readSize);
                messageDigest.update(buffer.array(), 0, readBytes);
                bytes -= readBytes;
            }
        }

        /* Ensure that the copy is persistent in the file system. */
        /* Persist the file itself. */
        BackupManager.forceFile(archiveFile.toPath());
        /* Persist its directory entry. */
        BackupManager.forceFile(archiveFile.getParentFile().toPath());

        return messageDigest.digest();
    }

    private File getFileFromURL(URL archiveURL) throws IOException {
        if (!"file".equalsIgnoreCase(archiveURL.getProtocol())) {
            throw new IllegalArgumentException("URL scheme must be file; not" +
                archiveURL.getProtocol());
        }
        final File archiveFile = new File(archiveURL.getFile());
        final File archiveParent = archiveFile.getParentFile();
        if (!archiveParent.exists()) {
            final boolean created = archiveParent.mkdirs();
            if (!created) {
                throw new IOException("Could not create parent directories " +
                                      " for " + archiveFile.getAbsolutePath());
            }
        }
        return archiveFile;
    }

    @Override
    public byte[] checksum(URL archiveURL)
        throws InterruptedException, IOException {

        requireInitialized();
        Objects.requireNonNull(archiveURL, "archiveFile arg must not be null");
        if (!"file".equalsIgnoreCase(archiveURL.getProtocol())) {
            throw new IllegalArgumentException("URL scheme must be file; not" +
                                               archiveURL.getProtocol());
        }

        final MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance(checksumAlg);
        } catch (NoSuchAlgorithmException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        }
        final File archiveFile = new File(archiveURL.getFile());
        long length = archiveFile.length();

        try (final FileInputStream inputStream =
            new FileInputStream(archiveFile)) {

            final ByteBuffer buffer = ByteBuffer.allocate(TRANSFER_BYTES);
            for (long bytes = length; bytes > 0;) {
                int readSize = (int)Math.min(TRANSFER_BYTES, bytes);
                int readBytes = inputStream.read(buffer.array(), 0, readSize);
                if (readBytes == -1) {
                    throw new IOException("Premature EOF. Was expecting: " +
                                          readSize);
                }
                messageDigest.update(buffer.array(), 0, readBytes);
                bytes -= readBytes;
            }
        }
        return messageDigest.digest();
    }

    private void requireInitialized() {
        if (initProperties == null) {
            throw new IllegalStateException(this.getClass().getName() +
                                            " is not initialized);");
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy