de.dentrassi.pm.maven.internal.MavenRepositoryChannelAggregator.java Source code

Java tutorial

Introduction

Here is the source code for de.dentrassi.pm.maven.internal.MavenRepositoryChannelAggregator.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
 *     Markus Rathgeb - support multiple POMs per artifact
 *******************************************************************************/
package de.dentrassi.pm.maven.internal;

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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import de.dentrassi.pm.VersionInformation;
import de.dentrassi.pm.aspect.aggregate.AggregationContext;
import de.dentrassi.pm.aspect.aggregate.ChannelAggregator;
import de.dentrassi.pm.common.ArtifactInformation;
import de.dentrassi.pm.common.MetaKey;
import de.dentrassi.pm.common.MetaKeys;
import de.dentrassi.pm.common.XmlHelper;
import de.dentrassi.pm.maven.ChannelData;
import de.dentrassi.pm.maven.MavenInformation;
import de.dentrassi.pm.system.SitePrefixService;

public class MavenRepositoryChannelAggregator implements ChannelAggregator {
    private final static Logger logger = LoggerFactory.getLogger(MavenRepositoryChannelAggregator.class);

    private final XmlHelper xml = new XmlHelper();

    private static final String NL = "\n";

    private final SitePrefixService sitePrefixService;

    public MavenRepositoryChannelAggregator(final SitePrefixService sitePrefixService) {
        this.sitePrefixService = sitePrefixService;
    }

    @Override
    public Map<String, String> aggregateMetaData(final AggregationContext context) throws Exception {
        final Map<String, ArtifactInformation> map = makeMap(context.getArtifacts());

        final Map<String, String> result = new HashMap<>();

        final ChannelData cs = new ChannelData();

        final Set<String> groupIds = new HashSet<>();

        for (final ArtifactInformation art : context.getArtifacts()) {
            final Collection<MavenInformation> infos = getInfos(art, map);

            if (logger.isDebugEnabled()) {
                logger.debug("Found {} coordinates for {}", infos.size(), art);
                for (final MavenInformation info : infos) {
                    logger.debug("   {}", info);
                }
            }

            for (final MavenInformation info : infos) {
                // add
                try {
                    cs.add(info, art);
                    groupIds.add(info.getGroupId());
                } catch (final IllegalStateException ex) {
                    // Cannot add the same information made name (info.makeName()) multiple times.
                    // First-come, first-served.

                    // but log it for debugging
                    logger.debug("Failed to add " + info, ex);
                }
            }
        }

        String json = cs.toJson();
        result.put("channel", json);

        json = cs.toString();
        context.createCacheEntry("channel", "channel.json", "application/json",
                new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)));
        context.createCacheEntry("repo-metadata", "repository-metadata.xml", "text/xml", (stream) -> {
            try {
                this.xml.write(makeRepoMetaData(context), stream);
            } catch (final Exception e) {
                throw new IOException(e);
            }
        });
        context.createCacheEntry("prefixes", "prefixes.txt", "text/plain",
                (stream) -> makePrefixes(stream, groupIds));

        return result;
    }

    private void makePrefixes(final OutputStream stream, final Set<String> groupIds) throws IOException {
        final OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);

        writer.write("## repository-prefixes/2.0" + NL);
        writer.write("#" + NL);
        writer.write("# Generated by Package Drone " + VersionInformation.VERSION + NL);

        final String[] groups = groupIds.toArray(new String[groupIds.size()]);
        Arrays.sort(groups);
        for (final String groupId : groups) {
            writer.write("/");
            writer.write(groupId.replace(".", "/"));
            writer.write(NL);
        }

        writer.close();
    }

    /**
     * Create the repository meta data file, for scraping
     *
     * @param context
     * @return the document
     */
    private Document makeRepoMetaData(final AggregationContext context) {
        final Document doc = this.xml.create();

        // create document

        final Element root = doc.createElement("repository-metadata");
        doc.appendChild(root);

        addElement(root, "version", "1.0.0");
        addElement(root, "id", context.getChannelId());
        addElement(root, "name", context.getChannelNameOrId());
        addElement(root, "layout", "maven2");
        addElement(root, "policy", "mixed");
        addElement(root, "url", makeUrl(context.getChannelId()));

        return doc;
    }

    private String makeUrl(final String channelId) {
        return String.format("%s/maven/%s", this.sitePrefixService.getSitePrefix(), channelId);
    }

    static Map<String, ArtifactInformation> makeMap(final Collection<ArtifactInformation> artifacts) {
        final Map<String, ArtifactInformation> result = new HashMap<>(artifacts.size());

        for (final ArtifactInformation art : artifacts) {
            result.put(art.getId(), art);
        }

        return result;
    }

    static Set<MavenInformation> getInfos(final ArtifactInformation art,
            final Map<String, ArtifactInformation> map) {
        final Set<MavenInformation> infos = new HashSet<>();

        {
            // first check if the artifact already has direct maven coordinates
            final MavenInformation coords = parseMavenCoordinates(art.getMetaData(), null);
            if (coords != null) {
                infos.add(coords);
            }
        }

        final Collection<ArtifactInformation> pomArts = findPomArtifacts(art, map);
        for (final ArtifactInformation pomArt : pomArts) {
            final MavenInformation coords = parseMavenCoordinates(pomArt.getMetaData(), art);
            if (coords != null) {
                infos.add(coords);
            }
        }

        return infos;
    }

    /**
     * Parse the maven coordinates from the provided meta data
     * <p>
     * If the parameter <code>refArtifact</code> is not <code>null</code> then
     * the extension loaded from the meta data will be overridden with the file
     * extension of the name of the provided <code>refArtifact</code>.
     * </p>
     *
     * @param metaData
     *            the metadata to use
     * @param refArtifact
     *            an optionally referenced artifact
     * @return the maven coordinates or <code>null</code>
     */
    private static MavenInformation parseMavenCoordinates(final Map<MetaKey, String> metaData,
            final ArtifactInformation refArtifact) {
        try {
            final MavenInformation info = new MavenInformation();
            MetaKeys.bind(info, metaData);

            if (info.getGroupId() == null || info.getArtifactId() == null || info.getVersion() == null) {
                return null;
            }

            if (refArtifact != null) {
                /*
                 * so we point to another artifact and have to override the extension with that name
                 *
                 * this is used on the case where the meta data is provided by the POM artifact and the
                 * JAR artifact is a slave to this meta data. In this case the "pom" extension has to
                 * be replaces with the extension "jar".
                 */
                final String ext = FilenameUtils.getExtension(refArtifact.getName());
                info.setExtension(ext);
            }

            return info;
        } catch (final Exception e) {
            logger.debug("Failed to parse maven coordinates", e);
            return null;
        }
    }

    /**
     * Return a collection with all POMs candidates for an artifact.
     *
     * @param art
     *            The artifact that POMs should be found.
     * @param artifacts
     *            A containing all artifacts that could be used. The key consist
     *            of the artifact id, the value of the artifact themselves.
     * @return a collection with all POM candidates, an empty collection if no
     *         candidates are found.
     */
    private static Collection<ArtifactInformation> findPomArtifacts(final ArtifactInformation art,
            final Map<String, ArtifactInformation> artifacts) {
        final Collection<ArtifactInformation> poms = new LinkedList<>();

        fillPomsFromChildren(poms, art, artifacts);

        return poms;
    }

    /**
     * Fill a collection with all POMs for the children of an artifact.
     *
     * @param poms
     *            the collection that should be filled
     * @param art
     *            the artifact that should be evaluated
     * @param artifacts
     *            A containing all artifacts that could be used. The key consist
     *            of the artifact id, the value of the artifact themselves.
     * @return the number of POMs that has been added
     */
    private static int fillPomsFromChildren(final Collection<ArtifactInformation> poms,
            final ArtifactInformation art, final Map<String, ArtifactInformation> artifacts) {
        int cnt = 0;

        for (final String childId : art.getChildIds()) {
            final ArtifactInformation child = artifacts.get(childId);
            if (child != null) {
                final String childName = child.getName();
                if (isPomFileName(childName)) {
                    poms.add(child);
                    ++cnt;
                }
            }
        }

        return cnt;
    }

    /**
     * Check if a file name indicates a POM file.
     *
     * @param fileName
     *            the name of the file
     * @return true if the file name indicates a POM file, otherwise false.
     */
    private static boolean isPomFileName(final String fileName) {
        return "pom.xml".equals(fileName) || "pom".equals(FilenameUtils.getExtension(fileName));
    }

}