org.basinmc.maven.plugins.bsdiff.DiffMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.basinmc.maven.plugins.bsdiff.DiffMojo.java

Source

/*
 * Copyright 2016 Johannes Donath <johannesd@torchmind.com>
 * and other copyright owners as documented in the project's IP log.
 *
 * 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.basinmc.maven.plugins.bsdiff;

import com.google.common.io.ByteStreams;

import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

import io.sigpipe.jbsdiff.DefaultDiffSettings;
import io.sigpipe.jbsdiff.Diff;
import io.sigpipe.jbsdiff.DiffSettings;
import io.sigpipe.jbsdiff.InvalidHeaderException;

/**
 * Provides a goal to Maven which is capable of generating binary diffs between two files where the
 * source file can be one of either a local file, a maven artifact (resolved against the local
 * project scope) or an arbitrary file on a web server.
 *
 * @author <a href="mailto:johannesd@torchmind.com">Johannes Donath</a>
 */
@Mojo(name = "diff", requiresProject = false, threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE)
@Immutable
@ThreadSafe
public class DiffMojo extends AbstractMojo {
    private static final Pattern DISPOSITION_PATTERN = Pattern.compile("filename\\s+=\\s+\"?(\\S+)\"?;?",
            Pattern.CASE_INSENSITIVE);

    // <editor-fold desc="Configuration Properties">
    @Parameter
    private ArtifactCoordinate sourceArtifact;
    @Parameter
    private File sourceFile;
    @Parameter
    private URL sourceURL;

    @Parameter(defaultValue = "${project.build.directory}")
    private File cacheDirectory;
    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.jar")
    private File target;

    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.bsdiff")
    private File outputFile;
    @Parameter(defaultValue = CompressorStreamFactory.XZ)
    private String compression;

    @Parameter(defaultValue = "true")
    private boolean attach;
    @Parameter
    private String classifier;
    // </editor-fold>

    // <editor-fold desc="Maven Components">
    @Parameter(property = "project", required = true, readonly = true)
    private MavenProject project;
    @Parameter(defaultValue = "${session}", readonly = true)
    private MavenSession session;

    @Component
    private ArtifactFactory artifactFactory;
    @Component
    private ArtifactResolver artifactResolver;
    @Component
    private MavenProjectHelper projectHelper;
    // </editor-fold>

    /**
     * {@inheritDoc}
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        final Path sourcePath = this.getSourcePath();

        String fileExtension = "bsdiff." + this.compression;
        String fileName = this.outputFile.getName();

        if (fileName.endsWith(".bsdiff")) {
            fileName += fileExtension.substring(6);
        } else {
            fileName += fileExtension;
        }

        File outputFile = this.outputFile.toPath().resolveSibling(fileName).toFile();

        try (InputStream sourceStream = new FileInputStream(sourcePath.toFile())) {
            try (InputStream targetStream = new FileInputStream(this.target)) {
                byte[] sourceBytes = ByteStreams.toByteArray(sourceStream);
                byte[] targetBytes = ByteStreams.toByteArray(targetStream);

                if (!Files.isDirectory(outputFile.toPath().getParent())) {
                    Files.createDirectories(outputFile.toPath().getParent());
                }

                try (OutputStream outputStream = new FileOutputStream(outputFile)) {
                    this.getLog().info("Generating binary diff");

                    DiffSettings settings = new DefaultDiffSettings(this.compression);
                    Diff.diff(sourceBytes, targetBytes, outputStream, settings);
                }
            }
        } catch (CompressorException ex) {
            throw new MojoFailureException("Failed to compress diff: " + ex.getMessage(), ex);
        } catch (InvalidHeaderException ex) {
            throw new MojoFailureException("Invalid header: " + ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new MojoFailureException("Failed to read/write source, target or output file: " + ex.getMessage(),
                    ex);
        }

        if (this.attach) {
            this.getLog().info("Attaching binary diff");

            if (this.classifier != null && !this.classifier.isEmpty()) {
                this.projectHelper.attachArtifact(this.project, fileExtension, this.classifier, outputFile);
                return; // TODO: Setting to attach both?
            }

            this.projectHelper.attachArtifact(this.project, fileExtension, outputFile);
        }
    }

    /**
     * Retrieves the location of the source artifact.
     */
    @Nonnull
    private Path getSourcePath() throws MojoFailureException {
        if (this.sourceFile != null) {
            return this.sourceFile.toPath();
        }

        if (this.sourceArtifact != null) {
            return this.resolveSourceArtifact();
        }

        return this.retrieveSourceFile();
    }

    /**
     * Resolves a local or remote maven artifact within the project scope.
     */
    @Nonnull
    private Path resolveSourceArtifact() throws MojoFailureException {
        Artifact artifact = this.sourceArtifact.toArtifact(this.artifactFactory);

        try {
            this.artifactResolver.resolve(artifact, this.project.getRemoteArtifactRepositories(),
                    this.session.getLocalRepository());
            return artifact.getFile().toPath();
        } catch (ArtifactNotFoundException ex) {
            throw new MojoFailureException("Could not locate artifact " + artifact.getGroupId() + ":"
                    + artifact.getArtifactId() + ":" + artifact.getVersion() + ":" + artifact.getType()
                    + (artifact.hasClassifier() ? ":" + artifact.getClassifier() : ""), ex);
        } catch (ArtifactResolutionException ex) {
            throw new MojoFailureException("Failed to resolve artifact: " + ex.getMessage(), ex);
        }
    }

    /**
     * Retrieves a remote artifact and stores it in a pre-defined cache directory.
     */
    @Nonnull
    private Path retrieveSourceFile() throws MojoFailureException {
        HttpClient client = HttpClients.createMinimal();
        String fileName;

        {
            String path = this.sourceURL.getPath();

            int i = path.lastIndexOf('/');
            fileName = path.substring(i + 1);
        }

        try {
            this.getLog().info("Downloading source artifact from " + this.sourceURL.toExternalForm());

            HttpGet request = new HttpGet(this.sourceURL.toURI());
            HttpResponse response = client.execute(request);

            if (response.containsHeader("Content-Disposition")) {
                String disposition = response.getLastHeader("Content-Disposition").getValue();
                Matcher matcher = DISPOSITION_PATTERN.matcher(disposition);

                if (matcher.matches()) {
                    fileName = URLDecoder.decode(matcher.group(1), "UTF-8");
                }
            }

            this.getLog().info("Storing " + fileName + " in cache directory");
            Path outputPath = this.cacheDirectory.toPath().resolve(fileName);

            if (!Files.isDirectory(outputPath.getParent())) {
                Files.createDirectories(outputPath.getParent());
            }

            try (InputStream inputStream = response.getEntity().getContent()) {
                try (ReadableByteChannel inputChannel = Channels.newChannel(inputStream)) {
                    try (FileChannel outputChannel = FileChannel.open(outputPath, StandardOpenOption.CREATE,
                            StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
                        outputChannel.transferFrom(inputChannel, 0, Long.MAX_VALUE);
                    }
                }
            }

            return outputPath;
        } catch (IOException ex) {
            throw new MojoFailureException("Failed to read/write source artifact: " + ex.getMessage(), ex);
        } catch (URISyntaxException ex) {
            throw new MojoFailureException("Invalid source URI: " + ex.getMessage(), ex);
        }
    }

    /**
     * Represents the absolute coordinates of an artifact.
     */
    public static class ArtifactCoordinate {
        @Parameter(required = true)
        private String groupId;
        @Parameter(required = true)
        private String artifactId;
        @Parameter(required = true)
        private String version;
        @Parameter
        private String type = "jar";
        @Parameter
        private String classifier;

        /**
         * Converts the artifact coordinates into a standard artifact which can be resolved against
         * local and remote Maven repositories.
         */
        @Nonnull
        public Artifact toArtifact(@Nonnull ArtifactFactory factory) {
            if (this.classifier != null && !this.classifier.isEmpty()) {
                return factory.createArtifactWithClassifier(this.groupId, this.artifactId, this.version, this.type,
                        this.classifier);
            }

            return factory.createBuildArtifact(this.groupId, this.artifactId, this.version, this.type);
        }
    }
}