Java tutorial
/******************************************************************************* * 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(); } }