org.artificer.atom.archive.ArtificerArchive.java Source code

Java tutorial

Introduction

Here is the source code for org.artificer.atom.archive.ArtificerArchive.java

Source

/*
 * Copyright 2012 JBoss Inc
 *
 * 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 org.artificer.atom.archive;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.artificer.atom.i18n.Messages;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactType;

import javax.xml.bind.JAXBException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Models the archive format defined in the S-RAMP Atom Binding document.
 *
 * @author eric.wittmann@redhat.com
 */
public class ArtificerArchive implements Serializable {

    private File originalFile;
    private boolean shouldDeleteOriginalFile;
    private File workDir;

    /**
     * Creates a new, empty S-RAMP archive.
     * @throws ArtificerArchiveException
     */
    public ArtificerArchive() throws ArtificerArchiveException {
        workDir = null;
        this.originalFile = null;
        try {
            workDir = createWorkDir();
        } catch (IOException e) {
            if (workDir != null && workDir.exists()) {
                try {
                    FileUtils.deleteDirectory(workDir);
                } catch (IOException e1) {
                }
            }
            throw new ArtificerArchiveException(Messages.i18n.format("FAILED_TO_CREATE_WORK_DIR"), e);
        }
    }

    /**
     * Creates an S-RAMP archive from an existing archive file.
     * @param file
     * @throws ArtificerArchiveException
     */
    public ArtificerArchive(File file) throws ArtificerArchiveException {
        this();
        this.originalFile = file;
        this.shouldDeleteOriginalFile = false;
        try {
            ArchiveUtils.unpackToWorkDir(this.originalFile, this.workDir);
        } catch (IOException e) {
            if (this.workDir != null) {
                try {
                    FileUtils.deleteDirectory(this.workDir);
                } catch (IOException e1) {
                }
            }
            throw new ArtificerArchiveException(Messages.i18n.format("FAILED_TO_UNPACK_ARCHIVE_TO_WORK_DIR"), e);
        }
    }

    /**
     * Creates an S-RAMP archive from an {@link InputStream}.  This will consume and close the
     * {@link InputStream}, creating a temporary local file that will be used as the basis for
     * the archive input.
     * @param input
     * @throws ArtificerArchiveException
     */
    public ArtificerArchive(InputStream input) throws ArtificerArchiveException {
        this();
        this.originalFile = null;
        this.shouldDeleteOriginalFile = true;

        try {
            this.originalFile = File.createTempFile("s-ramp-archive", ".zip");
            copyZipStream(input, this.originalFile);
            ArchiveUtils.unpackToWorkDir(this.originalFile, this.workDir);
        } catch (IOException e) {
            if (this.workDir != null) {
                try {
                    FileUtils.deleteDirectory(this.workDir);
                } catch (IOException e1) {
                }
            }
            if (this.originalFile != null && this.originalFile.exists()) {
                this.originalFile.delete();
            }
            throw new ArtificerArchiveException(Messages.i18n.format("FAILED_TO_UNPACK_ARCHIVE_TO_WORK_DIR"), e);
        }
    }

    /**
     * Create the working directory for this archive.
     * @throws IOException
     */
    private static File createWorkDir() throws IOException {
        File tempFile = File.createTempFile("s-ramp-archive", ".work");
        tempFile.delete();
        tempFile.mkdir();
        return tempFile;
    }

    /**
     * Copies the ZIP content from the input stream to the given output file.
     * @param zipStream
     * @param zipOutputFile
     * @throws IOException
     */
    private static void copyZipStream(InputStream zipStream, File zipOutputFile) throws IOException {
        OutputStream oStream = null;
        try {
            oStream = FileUtils.openOutputStream(zipOutputFile);
            IOUtils.copy(zipStream, oStream);
        } finally {
            IOUtils.closeQuietly(zipStream);
            IOUtils.closeQuietly(oStream);
        }
    }

    /**
     * The S-RAMP archive should always be closed when the client is done with it.  This will
     * clean up all temporary resources created by the archive.
     * @throws IOException
     */
    public void close() throws IOException {
        FileUtils.deleteDirectory(workDir);
        if (this.shouldDeleteOriginalFile) {
            this.originalFile.delete();
        }
    }

    /**
     * Close the archive quietly (eat any {@link IOException}).
     * @param archive
     */
    public static void closeQuietly(ArtificerArchive archive) {
        try {
            if (archive != null)
                archive.close();
        } catch (IOException e) {
        }
    }

    /**
     * Gets all of the entries found in this S-RAMP archive.  It does this by scanning the
     * archive looking for all *.atom files.  One entry will be returned for each *.atom
     * file found in the archive (assuming it has associated content and the *.atom file is
     * properly formatted).
     * @throws ArtificerArchiveException
     */
    public Collection<ArtificerArchiveEntry> getEntries() throws ArtificerArchiveException {
        Collection<File> files = FileUtils.listFiles(workDir, new String[] { "atom" }, true);
        Collection<ArtificerArchiveEntry> entries = new ArrayList<ArtificerArchiveEntry>(files.size());
        for (File metaDataFile : files) {
            String metaDataAbsPath = metaDataFile.getAbsolutePath();
            File contentFile = new File(metaDataAbsPath.substring(0, metaDataAbsPath.length() - 5));
            String path = contentFile.getAbsolutePath();
            path = path.substring(this.workDir.getAbsolutePath().length() + 1);
            path = path.replace('\\', '/'); // just in case we're in Windows :(
            entries.add(new ArtificerArchiveEntry(path, metaDataFile, contentFile));
        }
        return entries;
    }

    /**
     * Gets the content {@link InputStream} for the given S-RAMP archive entry.
     * @param entry the s-ramp archive entry
     * @return an {@link InputStream} over the artifact content or null if no content found (meta-data only)
     * @throws IOException
     */
    public InputStream getInputStream(ArtificerArchiveEntry entry) throws IOException {
        File artifactPath = new File(this.workDir, entry.getPath());
        if (artifactPath.exists())
            return FileUtils.openInputStream(artifactPath);
        else
            return null;
    }

    /**
     * Adds an entry to the S-RAMP archive.  This method will close the content
     * {@link InputStream}.
     * @param path the path in the archive (usually just the name of the artifact)
     * @param metaData the artifact meta-data
     * @param content the entry content (or null if a meta-data only entry)
     * @throws ArtificerArchiveException
     */
    public void addEntry(String path, BaseArtifactType metaData, InputStream content)
            throws ArtificerArchiveException {
        if (path == null)
            throw new ArtificerArchiveException(Messages.i18n.format("INVALID_ENTRY_PATH"));
        if (metaData == null)
            throw new ArtificerArchiveException(Messages.i18n.format("MISSING_META_DATA"));
        File metaDataFile = new File(this.workDir, path + ".atom");
        File contentFile = new File(this.workDir, path);
        if (metaDataFile.exists())
            throw new ArtificerArchiveException(Messages.i18n.format("ARCHIVE_ALREADY_EXISTS"));
        // Create any required parent directories
        metaDataFile.getParentFile().mkdirs();
        if (content != null)
            writeContent(contentFile, content);
        try {
            ArtificerArchiveJaxbUtils.writeMetaData(metaDataFile, metaData);
        } catch (JAXBException e) {
            throw new ArtificerArchiveException(e);
        }
    }

    /**
     * Updates an existing entry in the S-RAMP archive.  This method will close the content
     * {@link InputStream}.
     * @param entry the archive entry (or null if just udpating the content)
     * @param content the entry content (or null if just updating meta data)
     * @throws ArtificerArchiveException
     */
    public void updateEntry(ArtificerArchiveEntry entry, InputStream content) throws ArtificerArchiveException {
        if (entry.getPath() == null)
            throw new ArtificerArchiveException(Messages.i18n.format("INVALID_ENTRY_PATH"));
        File contentFile = new File(this.workDir, entry.getPath());
        File metaDataFile = new File(this.workDir, entry.getPath() + ".atom");

        if (content != null)
            writeContent(contentFile, content);
        if (entry.getMetaData() != null) {
            try {
                ArtificerArchiveJaxbUtils.writeMetaData(metaDataFile, entry.getMetaData());
            } catch (JAXBException e) {
                throw new ArtificerArchiveException(e);
            }
        }
    }

    /**
     * Writes the artifact content to the given working path.
     * @param workPath
     * @param content
     * @throws ArtificerArchiveException
     */
    private void writeContent(File workPath, InputStream content) throws ArtificerArchiveException {
        OutputStream outStream = null;
        try {
            outStream = new FileOutputStream(workPath);
            IOUtils.copy(content, outStream);
        } catch (Throwable t) {
            throw new ArtificerArchiveException(Messages.i18n.format("ERROR_WRITING_CONTENT"), t);
        } finally {
            IOUtils.closeQuietly(content);
            IOUtils.closeQuietly(outStream);
        }
    }

    /**
     * Packs up the current contents of the S-RAMP archive into a single (.zip) file and
     * returns a reference to it.  This method is guaranteed to either throw an Exception
     * or return a valid {@link File}.  It will never throw and leave a temporary file
     * behind.
     * @throws ArtificerArchiveException
     */
    public File pack() throws ArtificerArchiveException {
        try {
            File archiveFile = null;
            try {
                archiveFile = File.createTempFile("artificer-archive", ".sramp");
                FileOutputStream outputStream = FileUtils.openOutputStream(archiveFile);
                ZipOutputStream zipOutputStream = null;
                try {
                    zipOutputStream = new ZipOutputStream(outputStream);
                    Collection<ArtificerArchiveEntry> entries = getEntries();
                    for (ArtificerArchiveEntry entry : entries) {
                        packEntry(entry, zipOutputStream);
                    }
                } finally {
                    IOUtils.closeQuietly(zipOutputStream);
                }
            } catch (Throwable t) {
                // If anything goes wrong, make sure the File is cleaned up, as
                // we won't have another chance to do so.
                if (archiveFile != null && archiveFile.isFile())
                    archiveFile.delete();
                throw t;
            }
            return archiveFile;
        } catch (Throwable t) {
            throw new ArtificerArchiveException(Messages.i18n.format("ERROR_PACKING_ARCHIVE"), t);
        }
    }

    /**
     * Pack the given S-RAMP archive entry into the ZIP.
     * @param entry an s-ramp archive entry
     * @param zipOutputStream the zip file
     * @throws IOException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws URISyntaxException
     * @throws SecurityException
     * @throws IllegalArgumentException
     * @throws JAXBException
     */
    private void packEntry(ArtificerArchiveEntry entry, ZipOutputStream zipOutputStream)
            throws IOException, IllegalArgumentException, SecurityException, URISyntaxException,
            IllegalAccessException, InvocationTargetException, NoSuchMethodException, JAXBException {
        // Store the artifact content in the ZIP
        InputStream contentStream = getInputStream(entry);
        if (contentStream != null) {
            zipOutputStream.putNextEntry(new ZipEntry(entry.getPath()));
            try {
                IOUtils.copy(contentStream, zipOutputStream);
            } finally {
                IOUtils.closeQuietly(contentStream);
            }
            zipOutputStream.closeEntry();
        }

        // Store the meta-data in the ZIP
        zipOutputStream.putNextEntry(new ZipEntry(entry.getPath() + ".atom"));
        try {
            ArtificerArchiveJaxbUtils.writeMetaData(zipOutputStream, entry.getMetaData());
        } finally {
        }
        zipOutputStream.closeEntry();
    }

    /**
     * Gets a single entry in the archive by path.
     * @param archivePath the path of the entry within the archive
     * @return the archive entry, or null if not found
     */
    public ArtificerArchiveEntry getEntry(String archivePath) {
        File contentFile = new File(this.workDir, archivePath);
        File metaDataFile = new File(this.workDir, archivePath + ".atom");
        ArtificerArchiveEntry rval = null;
        if (metaDataFile.exists()) {
            rval = new ArtificerArchiveEntry(archivePath, metaDataFile, contentFile);
        }
        return rval;
    }

    /**
     * Returns true if the s-ramp archive contains an entry at the given path.
     * @param archivePath path to the entry within the archive
     * @return true if an entry exists at the path
     */
    public boolean containsEntry(String archivePath) {
        File metaDataFile = new File(this.workDir, archivePath + ".atom");
        return metaDataFile.exists();
    }

    /**
     * Removes the s-ramp archive entry at the given path if it exists.
     * @param archivePath path to the entry within the archive
     * @return true if an entry existed and was removed
     */
    public boolean removeEntry(String archivePath) {
        File metaDataFile = new File(this.workDir, archivePath + ".atom");
        File contentFile = new File(this.workDir, archivePath);
        if (metaDataFile.isFile()) {
            metaDataFile.delete();
            if (contentFile.isFile()) {
                contentFile.delete();
            }
            return true;
        }
        return false;
    }

}