de.dentrassi.pm.maven.ChannelData.java Source code

Java tutorial

Introduction

Here is the source code for de.dentrassi.pm.maven.ChannelData.java

Source

/*******************************************************************************
 * Copyright (c) 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package de.dentrassi.pm.maven;

import static de.dentrassi.pm.common.XmlHelper.addElement;
import static de.dentrassi.pm.common.XmlHelper.addElementFirst;

import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.scada.utils.str.StringHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import de.dentrassi.pm.common.ArtifactInformation;
import de.dentrassi.pm.common.MetaKey;
import de.dentrassi.pm.common.XmlHelper;

public class ChannelData {
    protected static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");

    private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(?<ts>[0-9]{8}-[0-9]{6})-1(?<bn>[0-9]+)");

    public static abstract class Node {
        public boolean isDirectory() {
            return false;
        }
    }

    public static class DirectoryNode extends Node {
        private final Map<String, Node> nodes = new HashMap<>();

        public Map<String, Node> getNodes() {
            return this.nodes;
        }

        @Override
        public boolean isDirectory() {
            return true;
        }
    }

    public abstract static class ContentNode extends Node {
        public abstract String getMimeType();

        public abstract byte[] getData();
    }

    public static class DataNode extends ContentNode {
        private final byte[] data;

        private final String mimeType;

        public DataNode(final byte[] data, final String mimeType) {
            this.data = data;
            this.mimeType = mimeType;
        }

        public DataNode(final String data, final String mimeType) {
            this.data = data.getBytes(StandardCharsets.UTF_8);
            this.mimeType = mimeType;
        }

        @Override
        public String getMimeType() {
            return this.mimeType;
        }

        @Override
        public byte[] getData() {
            return this.data;
        }
    }

    public static class ChecksumNode extends ContentNode {

        private final ContentNode node;

        private final String alg;

        public ChecksumNode(final ContentNode node, final String alg) {
            this.node = node;
            this.alg = alg;
        }

        @Override
        public String getMimeType() {
            return "text/plain";
        }

        @Override
        public byte[] getData() {
            try {
                final MessageDigest md = MessageDigest.getInstance(this.alg);
                final byte[] result = md.digest(this.node.getData());
                return StringHelper.toHex(result).getBytes(StandardCharsets.UTF_8);
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
        }

    }

    public static class ArtifactNode extends Node {
        private final String artifactId;

        public ArtifactNode(final String artifactId) {
            this.artifactId = artifactId;
        }

        public String getArtifactId() {
            return this.artifactId;
        }
    }

    public static class VersionMetadataNode extends ContentNode {
        private final List<MavenInformation> infos = new LinkedList<>();

        private final Map<MavenInformation, Date> timestamps = new HashMap<>();

        private final String groupId;

        private final String artifactId;

        private final String version;

        public VersionMetadataNode(final String groupId, final String artifactId, final String version) {
            this.groupId = groupId;
            this.artifactId = artifactId;
            this.version = version;
        }

        @Override
        public String getMimeType() {
            return "application/xml";
        }

        @Override
        public byte[] getData() {
            final XmlHelper xml = new XmlHelper();

            final Document doc = createMetaData(this.groupId, this.artifactId, this.version, this.infos,
                    this.timestamps);
            try {
                return xml.toData(doc);
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void add(final MavenInformation info, final Date date) {
            this.infos.add(info);
            this.timestamps.put(info, date);
        }
    }

    public static class ArtifactMetadataNode extends ContentNode {
        private final List<MavenInformation> infos = new LinkedList<>();

        private final String groupId;

        private final String artifactId;

        public ArtifactMetadataNode(final String groupId, final String artifactId) {
            this.groupId = groupId;
            this.artifactId = artifactId;
        }

        @Override
        public String getMimeType() {
            return "application/xml";
        }

        @Override
        public byte[] getData() {
            final XmlHelper xml = new XmlHelper();

            final Document doc = createMetaData(this.groupId, this.artifactId, this.infos);
            try {
                return xml.toData(doc);
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
        }

        public void add(final MavenInformation info, final Date date) {
            this.infos.add(info);
        }
    }

    private final DirectoryNode root = new DirectoryNode();

    public void add(final MavenInformation info, final ArtifactInformation art) {
        final String[] gn = info.getGroupId().split("\\.");
        final DirectoryNode groupNode = getGroup(gn);

        final DirectoryNode artifactBase = addDirNode(groupNode, info.getArtifactId());

        final ArtifactMetadataNode mdNode = addArtifactMetaDataNode(info, artifactBase);

        final DirectoryNode versionNode = addDirNode(artifactBase, info.getVersion());

        addNode(versionNode, info.makeName(), new ArtifactNode(art.getId()));

        addCheckSum(versionNode, info.makeName(), art, "md5");
        addCheckSum(versionNode, info.makeName(), art, "sha1");

        mdNode.add(info, art.getCreationTimestamp());

        if (info.isSnapshot()) {
            final VersionMetadataNode versionMd = addVersionMetaDataNode(info, versionNode);
            versionMd.add(info, art.getCreationTimestamp());
        }
    }

    protected <T extends ContentNode> T addMetaDataNode(final MavenInformation info, final DirectoryNode base,
            final Class<T> clazz, final Supplier<T> supp) {
        final Node n = base.getNodes().get("maven-metadata.xml");
        if (n == null) {
            final T result = addNode(base, "maven-metadata.xml", supp.get());

            addNode(base, "maven-metadata.xml.md5", new ChecksumNode(result, "MD5"));
            addNode(base, "maven-metadata.xml.sha1", new ChecksumNode(result, "SHA1"));

            return result;
        } else if (clazz.isAssignableFrom(n.getClass())) {
            return clazz.cast(n);
        } else {
            throw new IllegalStateException(
                    String.format("Invalid hierarchy. Someone blocked meta data entry: 'maven-metadata.xml'"));
        }
    }

    protected ArtifactMetadataNode addArtifactMetaDataNode(final MavenInformation info,
            final DirectoryNode artifactBase) {
        return addMetaDataNode(info, artifactBase, ArtifactMetadataNode.class,
                () -> new ArtifactMetadataNode(info.getGroupId(), info.getArtifactId()));
    }

    protected VersionMetadataNode addVersionMetaDataNode(final MavenInformation info,
            final DirectoryNode artifactBase) {
        return addMetaDataNode(info, artifactBase, VersionMetadataNode.class,
                () -> new VersionMetadataNode(info.getGroupId(), info.getArtifactId(), info.getVersion()));
    }

    protected static Document makeMetaData(final String groupId, final String artifactId,
            final BiConsumer<Document, Element> cons) {
        final XmlHelper xml = new XmlHelper();
        final Document doc = xml.create();
        final Element root = doc.createElement("metadata");
        doc.appendChild(root);

        addElement(root, "groupId", groupId);
        addElement(root, "artifactId", artifactId);

        final Element v = addElement(root, "versioning");

        cons.accept(doc, v);

        XmlHelper.addElement(v, "lastUpdated", DATE_FORMAT.format(new Date()));

        return doc;
    }

    public static Document createMetaData(final String groupId, final String artifactId, final String version,
            final List<MavenInformation> infos, final Map<MavenInformation, Date> timestamps) {
        return makeMetaData(groupId, artifactId, (doc, v) -> {

            // insert right before "versioning"
            final Element ver = doc.createElement("version");
            ver.setTextContent(version);
            v.getParentNode().insertBefore(ver, v);

            final Map<String, List<MavenInformation>> gi = new TreeMap<>();
            final TreeSet<String> snapshots = new TreeSet<>();

            // group by snapshot version
            for (final MavenInformation info : infos) {
                if (info.isSnapshot() && info.getSnapshotVersion() == null) {
                    continue;
                }

                snapshots.add(info.getSnapshotVersion());

                List<MavenInformation> list = gi.get(info.getSnapshotVersion());
                if (list == null) {
                    list = new LinkedList<>();
                    gi.put(info.getSnapshotVersion(), list);
                }
                list.add(info);
            }

            if (!gi.isEmpty()) {
                final Element svs = addElement(v, "snapshotVersions");
                for (final Map.Entry<String, List<MavenInformation>> entry : gi.entrySet()) {
                    for (final MavenInformation info : entry.getValue()) {
                        final Element sv = addElement(svs, "snapshotVersion");

                        addElement(sv, "extension", info.getExtension());
                        addElement(sv, "value", info.getSnapshotVersion());

                        if (info.getClassifier() != null && !info.getClassifier().isEmpty()) {
                            addElement(sv, "classifier", info.getClassifier());
                        }

                        final Date ts = timestamps.get(info);
                        if (ts != null) {
                            addElement(sv, "updated", DATE_FORMAT.format(ts)); // FIXME: replace with artifact date
                        }
                    }
                }

                {
                    final String latest = snapshots.last();

                    final Matcher m = SNAPSHOT_PATTERN.matcher(latest);
                    if (m.matches()) {
                        final Element s = addElementFirst(v, "snapshot");
                        addElement(s, "timestamp", m.group("ts"));
                        addElement(s, "buildNumber", m.group("bn"));
                    }
                }
            }
        });
    }

    public static Document createMetaData(final String groupId, final String artifactId,
            final List<MavenInformation> infos) {
        return makeMetaData(groupId, artifactId, (doc, v) -> {

            final Set<String> releases = new HashSet<>();
            final Set<String> all = new HashSet<>();

            for (final MavenInformation info : infos) {
                all.add(info.getVersion());
                if (info.isSnapshot()) {
                    continue;
                }

                releases.add(info.getVersion());
            }

            final List<String> allSorted = sorted(all);
            if (!all.isEmpty()) {
                final Element vs = addElement(v, "versions");
                for (final String release : allSorted) {
                    addElement(vs, "version", release);
                }
            }

            if (!releases.isEmpty()) {
                final List<String> releasesSorted = sorted(releases);

                final String releaseStr = best(releasesSorted);
                if (releaseStr != null) {
                    final Element release = addElementFirst(v, "release");
                    release.setTextContent(releaseStr);
                }
            }

            final String latestStr = best(allSorted);
            if (latestStr != null) {
                final Element latest = addElementFirst(v, "latest");
                latest.setTextContent(latestStr);
            }

        });
    }

    private static List<String> sorted(final Set<String> versions) {
        final List<String> list = new ArrayList<>(versions);
        Collections.sort(list);
        return list;
    }

    private static String best(final List<String> versions) {
        if (versions.isEmpty()) {
            return null;
        }

        return versions.get(versions.size() - 1);
    }

    private static void addCheckSum(final DirectoryNode versionNode, final String name,
            final ArtifactInformation art, final String string) {
        final String data = art.getMetaData().get(new MetaKey("hasher", string));
        if (data == null) {
            return;
        }

        addNode(versionNode, name + "." + string, new DataNode(data, "text/plain"));
    }

    private DirectoryNode getGroup(final String[] gn) {
        final LinkedList<String> dir = new LinkedList<>(Arrays.asList(gn));

        DirectoryNode current = this.root;
        while (!dir.isEmpty()) {
            current = addDirNode(current, dir.pollFirst());
        }

        return current;
    }

    private static <T extends Node> T addNode(final DirectoryNode current, final String seg, final T node) {
        if (current.nodes.containsKey(seg)) {
            throw new IllegalStateException(String.format("Invalid hierarchy. %s is already used.", seg));
        }

        current.nodes.put(seg, node);

        return node;
    }

    private DirectoryNode addDirNode(final DirectoryNode current, final String seg) {
        Node g = current.nodes.get(seg);

        if (g == null) {
            g = new DirectoryNode();
            current.nodes.put(seg, g);
            return (DirectoryNode) g;
        }

        if (g instanceof DirectoryNode) {
            return (DirectoryNode) g;
        }

        throw new IllegalStateException(
                String.format("Invalid group hierarchy. %s is of type %s.", seg, g.getClass()));
    }

    public Node findNode(final Deque<String> segs) {
        Node current = this.root;
        while (!segs.isEmpty()) {
            if (!(current instanceof DirectoryNode)) {
                return null;
            }

            final String n = segs.pollFirst();
            final Node node = ((DirectoryNode) current).nodes.get(n);
            if (node == null) {
                return null;
            }

            current = node;
        }

        return current;
    }

    public String toJson() {
        final Gson gson = makeGson(false);
        return gson.toJson(this);
    }

    @Override
    public String toString() {
        final Gson gson = makeGson(true);
        return gson.toJson(this);
    }

    public static ChannelData fromJson(final String json) {
        final Gson gson = makeGson(false);
        return gson.fromJson(json, ChannelData.class);
    }

    public static ChannelData fromReader(final Reader reader) {
        final Gson gson = makeGson(false);
        return gson.fromJson(reader, ChannelData.class);
    }

    /**
     * Make an appropriate Gson parser to processing ChannelData instances
     *
     * @param pretty
     *            if the gson output should be "pretty printed"
     * @return the new gson instance
     */
    public static Gson makeGson(final boolean pretty) {
        final GsonBuilder gb = new GsonBuilder();

        if (pretty) {
            gb.setPrettyPrinting();
        }

        gb.registerTypeAdapter(Node.class, new NodeAdapter());
        gb.registerTypeAdapter(byte[].class, new ByteArrayAdapter());

        return gb.create();
    }

}