de.egore911.versioning.deployer.performer.PerformExtraction.java Source code

Java tutorial

Introduction

Here is the source code for de.egore911.versioning.deployer.performer.PerformExtraction.java

Source

/*
 * Copyright 2013  Christoph Brill <egore911@gmail.com>
 *
 * 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 2 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/>.
 */
package de.egore911.versioning.deployer.performer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import de.egore911.versioning.deployer.util.ExtractionPair;
import de.egore911.versioning.util.UrlUtil;

/**
 * Performs an exctration copy operation as told by the versioning web
 * application.
 * 
 * @author Christoph Brill &lt;egore911@gmail.com&gt;
 */
public class PerformExtraction {

    private static final Logger LOG = LoggerFactory.getLogger(PerformExtraction.class);

    private static final Map<String, Pattern> patternCache = new HashMap<>();

    private final XPathExpression extractXpath;
    private final XPathExpression urlXpath;
    private final XPathExpression extractionsExtractionXpath;
    private final XPathExpression sourceXpath;
    private final XPathExpression destinationXpath;

    public PerformExtraction(XPath xPath) throws XPathExpressionException {
        extractXpath = xPath.compile("extract");
        urlXpath = xPath.compile("url/text()");
        extractionsExtractionXpath = xPath.compile("extractions/extraction");
        sourceXpath = xPath.compile("source/text()");
        destinationXpath = xPath.compile("destination/text()");
    }

    public void perform(Node serverDeploymentsDeploymentNode) throws XPathExpressionException {
        NodeList extractionOperations = (NodeList) extractXpath.evaluate(serverDeploymentsDeploymentNode,
                XPathConstants.NODESET);
        for (int j = 0; j < extractionOperations.getLength(); j++) {
            Node extractionOperation = extractionOperations.item(j);
            String uri = (String) urlXpath.evaluate(extractionOperation, XPathConstants.STRING);
            NodeList extractions = (NodeList) extractionsExtractionXpath.evaluate(extractionOperation,
                    XPathConstants.NODESET);
            List<ExtractionPair> exts = new ArrayList<>();
            for (int k = 0; k < extractions.getLength(); k++) {
                Node extraction = extractions.item(k);
                String source = (String) sourceXpath.evaluate(extraction, XPathConstants.STRING);
                String destination = (String) destinationXpath.evaluate(extraction, XPathConstants.STRING);
                exts.add(new ExtractionPair(source, destination));
            }
            extract(uri, exts);
        }
    }

    private static boolean extract(String uri, List<ExtractionPair> extractions) {
        URL url;
        try {
            url = new URL(uri);
        } catch (MalformedURLException e) {
            LOG.error("Invalid URI: {}", e.getMessage(), e);
            return false;
        }
        try {
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            int response = connection.getResponseCode();

            int lastSlash = uri.lastIndexOf('/');
            if (lastSlash < 0) {
                LOG.error("Invalid URI: {}", uri);
                return false;
            }

            int lastDot = uri.lastIndexOf('.');
            if (lastDot < 0) {
                LOG.error("Invalid URI: {}", uri);
                return false;
            }

            File downloadFile = File.createTempFile(uri.substring(lastSlash + 1), uri.substring(lastDot + 1));
            downloadFile.deleteOnExit();

            if (response == HttpURLConnection.HTTP_OK) {
                try (InputStream in = connection.getInputStream();
                        FileOutputStream out = new FileOutputStream(downloadFile)) {
                    IOUtils.copy(in, out);
                }
                LOG.debug("Downloaded {} to {}", url, downloadFile.getAbsolutePath());

                Set<ExtractionPair> usedExtractions = new HashSet<>();

                // Perform extractions
                try (ZipFile zipFile = new ZipFile(downloadFile)) {
                    Enumeration<? extends ZipEntry> entries = zipFile.entries();
                    while (entries.hasMoreElements()) {
                        ZipEntry entry = entries.nextElement();

                        // Only extract files
                        if (entry.isDirectory()) {
                            continue;
                        }

                        for (ExtractionPair extraction : extractions) {
                            String sourcePattern = extraction.source;
                            if (FilenameUtils.wildcardMatch(entry.getName(), sourcePattern)) {
                                usedExtractions.add(extraction);
                                LOG.debug("Found matching file {} for source pattern {}", entry.getName(),
                                        sourcePattern);
                                String filename = getSourcePatternMatch(entry.getName(), sourcePattern);
                                // Workaround: If there is no matcher in 'sourcePattern' it will return the
                                // complete path. Strip it down to the filename
                                if (filename.equals(entry.getName())) {
                                    int lastIndexOf = filename.lastIndexOf('/');
                                    if (lastIndexOf >= 0) {
                                        filename = filename.substring(lastIndexOf + 1);
                                    }
                                }
                                String name = UrlUtil.concatenateUrlWithSlashes(extraction.destination, filename);
                                FileUtils.forceMkdir(new File(name).getParentFile());
                                try (InputStream in = zipFile.getInputStream(entry);
                                        FileOutputStream out = new FileOutputStream(name)) {
                                    IOUtils.copy(in, out);
                                }
                                LOG.debug("Extracted {} to {} from {}", entry.getName(), name, uri);
                            }
                        }
                    }
                }

                for (ExtractionPair extraction : extractions) {
                    if (!usedExtractions.contains(extraction)) {
                        LOG.debug("Extraction {} to {} not used on {}", extraction.source, extraction.destination,
                                uri);
                    }
                }

                return true;
            } else {
                LOG.error("Could not download file: {}", uri);
                return false;
            }
        } catch (IOException e) {
            LOG.error("Could not download file: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * Returns the matching part of the entry's name.
     * 
     * FIXME assumes that the '*' is only used for matching files, not
     * directories!
     */
    private static String getSourcePatternMatch(String entryName, String sourcePattern) {

        // Create a regular expression pattern for the given sourcePattern
        Pattern pattern = patternCache.get(sourcePattern);
        if (pattern == null) {
            // Pattern compilation is expensive, so cache it
            pattern = Pattern.compile(transformSourcePatternToRegularExpression(sourcePattern));
            patternCache.put(sourcePattern, pattern);
        }

        // Perform the actual replacement
        Matcher matcher = pattern.matcher(entryName);
        StringBuffer buffer = new StringBuffer();
        if (matcher.matches()) {
            if (matcher.groupCount() == 1) {
                matcher.appendReplacement(buffer, matcher.group(1));
            }
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }

    /**
     * Use the given source pattern (e.g. "/var/lib/tomcat7/war/*") and
     * transform it into a grouping regular expression (e.g.
     * "/var/lib/tomcat7/war/(.*)"). You get exactly one group per '*'.
     * Currently only one '*' is supported. '?' are also not supported.
     */
    private static String transformSourcePatternToRegularExpression(String sourcePattern) {
        if (StringUtils.countMatches(sourcePattern, "?") > 0) {
            throw new IllegalArgumentException("No support for '?' yet.");
        }

        switch (StringUtils.countMatches(sourcePattern, "*")) {
        case 0:
            // No match, nothing to do
            return sourcePattern;
        case 1:
            // One match, we're fine
            return sourcePattern.replace(".", "\\.").replace("*", "(.*") + ")";
        default:
            // More than one match, crash and burn
            throw new IllegalArgumentException("No support for multiple '*' yet.");
        }
    }

}