net.oneandone.maven.plugins.prerelease.core.Archive.java Source code

Java tutorial

Introduction

Here is the source code for net.oneandone.maven.plugins.prerelease.core.Archive.java

Source

/**
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * 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 net.oneandone.maven.plugins.prerelease.core;

import net.oneandone.maven.plugins.prerelease.util.ChangesXml;
import net.oneandone.sushi.fs.DirectoryNotFoundException;
import net.oneandone.sushi.fs.FileNotFoundException;
import net.oneandone.sushi.fs.ListException;
import net.oneandone.sushi.fs.MkfileException;
import net.oneandone.sushi.fs.OnShutdown;
import net.oneandone.sushi.fs.file.FileNode;
import net.oneandone.sushi.xml.XmlException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * The archive stores prereleases for one given groupId/artifactId.
 *  Prerelease directories have the svn revision number as their directory name; it may be a symlink to an arbitrary location
 */
//
// primaryStorage             <- defaults to ~/.m2/prereleases
//   groupId/artifactId.LOCK  <- optional, indicates that a process operates on this archive
//   groupId/artifactId/      <- archive directory
//    |- revision1           <- prerelease directory, ready to promote; promoting the prerelease removes this directory
//    |     |- tags
//    |     |    - <tagname>
//    |     |- artifacts
//    |      - prerelease.properties
//    |- revision2
//    :
//    |- REMOVE              <- a renamed prerelease directory
//    :     :                   optional - only when the last create call failed (because the mvn call failed) or promote succeeded
//    :     |- CAUSE         <- why this directory is to be removed
//    :     :
// secondaryStorage
//    :
// tertiaryStorage
//    :
public class Archive implements AutoCloseable {
    public static List<FileNode> directories(List<FileNode> storages, MavenProject project) {
        List<FileNode> directories;

        directories = new ArrayList<>(storages.size());
        for (FileNode storage : storages) {
            directories.add(storage.join(project.getGroupId(), project.getArtifactId()));
        }
        return directories;
    }

    public static Archive tryOpen(List<FileNode> directories) {
        try {
            return open(directories, -1, null);
        } catch (IOException e) {
            return null;
        }
    }

    public static Archive open(List<FileNode> directories, int timeout, Log log) throws IOException {
        Archive archive;

        archive = new Archive(directories);
        archive.open(timeout, log);
        return archive;
    }

    private final List<FileNode> directories;
    private boolean opened = false;
    private boolean closed = false;

    private Archive(List<FileNode> directories) {
        if (directories.size() == 0) {
            throw new IllegalArgumentException();
        }
        this.directories = directories;
    }

    public Target target(long revision) {
        String name;
        FileNode prerelease;

        name = Long.toString(revision);
        for (int i = directories.size() - 1; i >= 0; i--) {
            prerelease = directories.get(i).join(name);
            if (i == 0 || prerelease.exists()) {
                return new Target(prerelease, revision);
            }
        }
        throw new IllegalStateException();
    }

    /** */
    public TreeMap<Long, FileNode> list() throws ListException, DirectoryNotFoundException {
        TreeMap<Long, FileNode> result;
        long revision;

        result = new TreeMap<>();
        for (FileNode directory : directories) {
            if (directory.exists()) {
                for (FileNode prerelease : directory.list()) {
                    if (!prerelease.getName().equals(Target.REMOVE)) {
                        revision = Long.parseLong(prerelease.getName());
                        result.put(revision, prerelease);
                    }
                }
            }
        }
        return result;
    }

    public long latest() throws ListException, DirectoryNotFoundException {
        return list().lastKey();
    }

    //--

    private FileNode lockFile() {
        FileNode primary;

        primary = directories.get(0);
        return primary.getParent().join(primary.getName() + ".LOCK");
    }

    /**
     * @param timeout in seconds; -1 to try only once and never wait.
     * @param log may be null
     */
    private void open(int timeout, Log log) throws IOException {
        FileNode file;
        int seconds;

        if (opened) {
            throw new IllegalStateException();
        }
        file = lockFile();
        try {
            seconds = 0;
            while (true) {
                // every time - if someone wiped the primary storage directory
                file.getParent().mkdirsOpt();
                try {
                    file.mkfile();
                    OnShutdown.get().deleteAtExit(file);
                    opened = true;
                    file.writeString(Integer.toString(pid()));
                    if (log != null) {
                        log.debug("locked for pid " + pid());
                    }
                    return;
                } catch (MkfileException e) {
                    if (seconds > timeout) {
                        if (log != null) {
                            log.warn("Lock timed out after " + seconds + "s.");
                        }
                        throw e;
                    }
                    if (seconds % 10 == 0) {
                        if (log != null) {
                            log.info("Waiting for " + file + ": " + seconds + "s");
                            log.debug(e);
                        }
                    }
                    seconds++;
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            if (log != null) {
                log.warn("interrupted");
            }
        }
    }

    @Override
    public void close() throws Exception {
        FileNode file;

        if (!opened) {
            throw new IllegalStateException("not opened");
        }
        if (closed) {
            throw new IllegalStateException("already closed");
        }
        file = lockFile();
        file.deleteFile();
        // because another thread waiting for this lock might create this file again.
        // The shutdown hook must not delete the file created by this other thread.
        OnShutdown.get().dontDeleteAtExit(file);
        closed = true;
    }

    //--

    private static int pid = 0;

    public static int pid() {
        String str;
        int idx;

        if (pid == 0) {
            // see http://blog.igorminar.com/2007/03/how-java-application-can-discover-its.html?m=1
            str = ManagementFactory.getRuntimeMXBean().getName();
            idx = str.indexOf('@');
            if (idx == -1) {
                throw new IllegalStateException("cannot guess pid from " + str);
            }
            pid = Integer.parseInt(str.substring(0, idx));
        }
        return pid;
    }

    /** @return true when a changes file was found */
    public static boolean adjustChangesOpt(FileNode workingCopy, String version)
            throws XmlException, IOException, SAXException {
        ChangesXml changes;

        try {
            changes = ChangesXml.load(workingCopy);
        } catch (FileNotFoundException e) {
            return false;
        }
        changes.releaseDate(version, new Date());
        changes.save();
        return true;
    }

    /**
     * @param keep number of prereleases after this method
     */
    public void wipe(int keep) throws IOException {
        TreeMap<Long, FileNode> prereleases;
        FileNode d;

        if (keep < 1) {
            throw new IllegalArgumentException("keep " + keep);
        }
        for (FileNode directory : directories) {
            d = directory.join(Target.REMOVE);
            if (d.isDirectory()) {
                d.deleteTree();
            }
        }
        prereleases = list();
        while (prereleases.size() > keep) {
            prereleases.remove(prereleases.firstKey()).deleteTree();
        }
    }
}