org.overlord.sramp.server.mvn.services.MavenFacadeServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.overlord.sramp.server.mvn.services.MavenFacadeServlet.java

Source

/*
 * Copyright 2014 JBoss Inc
 *
 * 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.overlord.sramp.server.mvn.services;

import org.apache.commons.io.IOUtils;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactType;
import org.overlord.sramp.common.ArtifactType;
import org.overlord.sramp.common.SrampConfig;
import org.overlord.sramp.common.SrampModelUtils;
import org.overlord.sramp.common.maven.MavenGavInfo;
import org.overlord.sramp.common.maven.MavenUtil;
import org.overlord.sramp.server.ArtifactServiceImpl;
import org.overlord.sramp.server.QueryServiceImpl;
import org.overlord.sramp.server.i18n.Messages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Provides a Maven repository "facade" to be used when deploying Maven-based artifacts.
 *
 * @author eric.wittmann@redhat.com
 * @author Brett Meyer
 * @author David Virgil Naranjo
 */
public class MavenFacadeServlet extends HttpServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(MavenFacadeServlet.class);
    private static final boolean SNAPSHOT_ALLOWED = SrampConfig.isSnapshotAllowed();

    private final ArtifactServiceImpl artifactService = new ArtifactServiceImpl();
    private final QueryServiceImpl queryService = new QueryServiceImpl();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            MavenGavInfo gavInfo = MavenGavInfo.fromUrl(req.getRequestURI());

            if (gavInfo.isMavenMetaData() && gavInfo.getVersion() == null) {
                writeResponse(doGenerateArtifactDirMavenMetaData(gavInfo), gavInfo, resp);
            } else if (gavInfo.isMavenMetaData() && gavInfo.getVersion() != null) {
                writeResponse(doGenerateSnapshotMavenMetaData(gavInfo), gavInfo, resp);
            } else if (gavInfo.isHash()) {
                writeResponse(doGetHash(gavInfo, req), gavInfo, resp);
            } else {
                writeResponse(findExistingArtifact(gavInfo), gavInfo, resp);
            }
        } catch (MavenRepositoryException e) {
            LOGGER.warn(Messages.i18n.format("MAVEN_GET_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        } catch (Exception e) {
            LOGGER.error(Messages.i18n.format("MAVEN_GET_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    private void writeResponse(String rval, MavenGavInfo gavInfo, HttpServletResponse resp) throws Exception {
        if (rval != null) {
            resp.addHeader("Content-Disposition", "attachment; filename=" + gavInfo.getName());
            resp.getWriter().write(rval);
            resp.getWriter().flush();
            resp.getWriter().close();
        }
    }

    private void writeResponse(BaseArtifactType artifact, MavenGavInfo gavInfo, HttpServletResponse resp)
            throws Exception {
        if (artifact != null) {
            ArtifactType artifactType = ArtifactType.valueOf(artifact);
            resp.setContentType(artifactType.getMimeType());
            resp.addHeader("Content-Disposition", "attachment; filename=" + gavInfo.getName());
            IOUtils.copy(artifactService.getContent(artifactType, artifact), resp.getOutputStream());
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            MavenGavInfo gavInfo = MavenGavInfo.fromUrl(req.getRequestURI());
            upload(gavInfo, req);
        } catch (MavenRepositoryException e) {
            LOGGER.warn(Messages.i18n.format("MAVEN_UPLOAD_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        } catch (Exception e) {
            LOGGER.error(Messages.i18n.format("MAVEN_UPLOAD_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            MavenGavInfo gavInfo = MavenGavInfo.fromUrl(req.getRequestURI());
            upload(gavInfo, req);
        } catch (MavenRepositoryException e) {
            LOGGER.warn(Messages.i18n.format("MAVEN_UPLOAD_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        } catch (Exception e) {
            LOGGER.error(Messages.i18n.format("MAVEN_UPLOAD_ERROR"), e);
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
        }
    }

    /**
     * Generates the maven-metadata.xml file dynamically for a given groupId/artifactId pair.  This will
     * list all of the versions available for that groupId+artifactId, along with the latest release and
     * snapshot versions.
     * @param gavInfo
     */
    private String doGenerateArtifactDirMavenMetaData(MavenGavInfo gavInfo) throws Exception {
        List<BaseArtifactType> artifacts = queryService.query("/s-ramp[@maven.groupId = '" + gavInfo.getGroupId()
                + "' and @maven.artifactId = '" + gavInfo.getArtifactId() + "']", "createdTimestamp", true);
        if (artifacts.size() == 0) {
            return null;
        }

        String groupId = gavInfo.getGroupId();
        String artifactId = gavInfo.getArtifactId();
        String latest = null;
        String release = null;
        String lastUpdated = null;

        LinkedHashSet<String> versions = new LinkedHashSet<String>();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        for (BaseArtifactType artifact : artifacts) {
            String version = SrampModelUtils.getCustomProperty(artifact, "maven.version");
            if (versions.add(version)) {
                latest = version;
                if (!version.endsWith("-SNAPSHOT")) {
                    release = version;
                }
            }
            lastUpdated = format.format(artifact.getCreatedTimestamp().toGregorianCalendar().getTime());
        }

        StringBuilder mavenMetadata = new StringBuilder();
        mavenMetadata.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        mavenMetadata.append("<metadata>\n");
        mavenMetadata.append("  <groupId>").append(groupId).append("</groupId>\n");
        mavenMetadata.append("  <artifactId>").append(artifactId).append("</artifactId>\n");
        mavenMetadata.append("  <versioning>\n");
        mavenMetadata.append("    <latest>").append(latest).append("</latest>\n");
        mavenMetadata.append("    <release>").append(release).append("</release>\n");
        mavenMetadata.append("    <versions>\n");
        for (String version : versions) {
            mavenMetadata.append("      <version>").append(version).append("</version>\n");
        }
        mavenMetadata.append("    </versions>\n");
        mavenMetadata.append("    <lastUpdated>").append(lastUpdated).append("</lastUpdated>\n");
        mavenMetadata.append("  </versioning>\n");
        mavenMetadata.append("</metadata>\n");

        if (!gavInfo.isHash()) {
            return mavenMetadata.toString();
        } else {
            return generateHash(mavenMetadata.toString(), gavInfo.getHashAlgorithm());
        }
    }

    /**
     * Generates the maven-metadata.xml file dynamically for a given groupId/artifactId/snapshot-version.
     * This will list all of the snapshot versions available.
     * @param gavInfo
     * @throws Exception
     */
    private String doGenerateSnapshotMavenMetaData(MavenGavInfo gavInfo) throws Exception {
        List<BaseArtifactType> artifacts = queryService.query(
                "/s-ramp[@maven.groupId = '" + gavInfo.getGroupId() + "'" + " and @maven.artifactId = '"
                        + gavInfo.getArtifactId() + "'" + " and @maven.version = '" + gavInfo.getVersion() + "']",
                "createdTimestamp", true);
        if (artifacts.size() == 0) {
            return null;
        }

        SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss");
        SimpleDateFormat updatedFormat = new SimpleDateFormat("yyyyMMddHHmmss");

        StringBuilder snapshotVersions = new StringBuilder();
        snapshotVersions.append("    <snapshotVersions>\n");
        Set<String> processed = new HashSet<String>();
        Calendar latestDate = null;
        for (BaseArtifactType artifact : artifacts) {
            String extension = SrampModelUtils.getCustomProperty(artifact, "maven.type");
            String classifier = SrampModelUtils.getCustomProperty(artifact, "maven.classifier");
            String value = gavInfo.getVersion();
            Calendar updatedDate = artifact.getLastModifiedTimestamp().toGregorianCalendar();
            String updated = updatedFormat.format(updatedDate.getTime());
            String pkey = classifier + "::" + extension;
            if (processed.add(pkey)) {
                snapshotVersions.append("      <snapshotVersion>\n");
                if (classifier != null)
                    snapshotVersions.append("        <classifier>").append(classifier).append("</classifier>\n");
                snapshotVersions.append("        <extension>").append(extension).append("</extension>\n");
                snapshotVersions.append("        <value>").append(value).append("</value>\n");
                snapshotVersions.append("        <updated>").append(updated).append("</updated>\n");
                snapshotVersions.append("      </snapshotVersion>\n");
                if (latestDate == null || latestDate.before(updatedDate)) {
                    latestDate = updatedDate;
                }
            }
        }
        snapshotVersions.append("    </snapshotVersions>\n");

        String groupId = gavInfo.getGroupId();
        String artifactId = gavInfo.getArtifactId();
        String version = gavInfo.getVersion();
        String lastUpdated = updatedFormat.format(latestDate.getTime());

        StringBuilder mavenMetadata = new StringBuilder();
        mavenMetadata.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        mavenMetadata.append("<metadata>\n");
        mavenMetadata.append("  <groupId>").append(groupId).append("</groupId>\n");
        mavenMetadata.append("  <artifactId>").append(artifactId).append("</artifactId>\n");
        mavenMetadata.append("  <version>").append(version).append("</version>\n");
        mavenMetadata.append("  <versioning>\n");
        mavenMetadata.append("    <snapshot>\n");
        mavenMetadata.append("      <timestamp>").append(timestampFormat.format(latestDate.getTime()))
                .append("</timestamp>\n");
        mavenMetadata.append("      <buildNumber>1</buildNumber>\n");
        mavenMetadata.append("    </snapshot>\n");
        mavenMetadata.append("    <lastUpdated>").append(lastUpdated).append("</lastUpdated>\n");
        mavenMetadata.append(snapshotVersions.toString());
        mavenMetadata.append("  </versioning>\n");
        mavenMetadata.append("</metadata>\n");

        if (!gavInfo.isHash()) {
            return mavenMetadata.toString();
        } else {
            return generateHash(mavenMetadata.toString(), gavInfo.getHashAlgorithm());
        }
    }

    /**
     * Gets the hash data from the s-ramp repository for use by Maven.
     * @param gavInfo
     * @param req
     * @throws Exception
     */
    private String doGetHash(MavenGavInfo gavInfo, HttpServletRequest req) throws Exception {
        int hashExtensionLength;
        String hashPropName;
        if (gavInfo.getType().endsWith(".md5")) {
            hashExtensionLength = 4;
            hashPropName = "maven.hash.md5";
        } else {
            hashExtensionLength = 5;
            hashPropName = "maven.hash.sha1";
        }

        MavenGavInfo primaryGavInfo = gavWithoutHash(req, hashExtensionLength);
        BaseArtifactType artifact = findExistingArtifact(primaryGavInfo);
        if (artifact == null) {
            return null;
        }

        return SrampModelUtils.getCustomProperty(artifact, hashPropName);
    }

    /**
     * Finds an existing artifact in the s-ramp repository that matches the type and GAV information.
     * @param gavInfo
     * @return an s-ramp artifact (if found) or null (if not found)
     * @throws Exception
     */
    private BaseArtifactType findExistingArtifact(MavenGavInfo gavInfo) throws Exception {
        BaseArtifactType artifact = findExistingArtifactByGAV(gavInfo);
        if (artifact == null)
            artifact = findExistingArtifactByUniversal(gavInfo);
        return artifact;
    }

    /**
     * Finds an existing artifact in the s-ramp repository that matches the GAV information.
     * @param gavInfo
     * @return an s-ramp artifact (if found) or null (if not found)
     * @throws Exception
     */
    private BaseArtifactType findExistingArtifactByGAV(MavenGavInfo gavInfo) throws Exception {
        String query = MavenUtil.gavQuery(gavInfo);

        List<BaseArtifactType> artifacts = queryService.query(query, "createdTimestamp", false);
        if (artifacts.size() > 0) {
            for (BaseArtifactType artifact : artifacts) {
                // If no classifier in the GAV info, only return the artifact that also has no classifier
                // TODO replace this with "not(@maven.classifer)" in the query, then force the result set to return 2 items (expecting only 1)
                if (gavInfo.getClassifier() == null) {
                    String artyClassifier = SrampModelUtils.getCustomProperty(artifact, "maven.classifier");
                    if (artyClassifier == null) {
                        return artifact;
                    }
                } else {
                    // If classifier was supplied in the GAV info, we'll get the first artifact <shrug>
                    return artifact;
                }
            }
        }
        return null;
    }

    /**
     * Finds an existing artifact in the s-ramp repository using 'universal' form.  This allows
     * any artifact in the s-ramp repository to be referenced as a Maven dependency using the
     * model.type and UUID of the artifact.
     * @param gavInfo
     * @return an existing s-ramp artifact (if found) or null (if not found)
     * @throws Exception
     */
    private BaseArtifactType findExistingArtifactByUniversal(MavenGavInfo gavInfo) throws Exception {
        String artifactType = gavInfo.getGroupId().substring(gavInfo.getGroupId().indexOf('.') + 1);
        String uuid = gavInfo.getArtifactId();
        try {
            return artifactService.getMetaData(ArtifactType.valueOf(artifactType, true), uuid);
        } catch (Exception e) {
            // Eat it.  If this wasn't model.type, it'll be a real groupId, which is *not* a valid extended type.
            // The server will through an exception if it's not completely alphanumeric.
            return null;
        }
    }

    /**
     * Common put implementation.  Handles firing events and ultimately sending the data via the
     * s-ramp client.
     * @param gavInfo
     * @param req
     * @throws Exception
     */
    private void upload(MavenGavInfo gavInfo, HttpServletRequest req) throws Exception {
        if (SNAPSHOT_ALLOWED || !gavInfo.isSnapshot()) {
            if (!gavInfo.getName().contains("maven-metadata.xml")) {
                if (gavInfo.isHash()) {
                    uploadHash(gavInfo, req);
                } else {
                    uploadArtifact(gavInfo, req);
                }
            }
        } else {
            throw new MavenRepositoryException(Messages.i18n.format("MAVEN_UPLOAD_SNAPSHOT"));
        }
    }

    /**
     * Updates an artifact by storing its hash value as an S-RAMP property.
     * @param gavInfo
     * @param req
     * @throws Exception
     */
    private void uploadHash(MavenGavInfo gavInfo, HttpServletRequest req) throws Exception {
        int hashExtensionLength;
        String hashPropName;
        if (gavInfo.getType().endsWith(".md5")) {
            hashExtensionLength = 4;
            hashPropName = "maven.hash.md5";
        } else {
            hashExtensionLength = 5;
            hashPropName = "maven.hash.sha1";
        }
        String hashValue = IOUtils.toString(req.getInputStream());

        // Re-fetch the artifact meta-data in case it changed on the server since we uploaded it.
        MavenGavInfo primaryGavInfo = gavWithoutHash(req, hashExtensionLength);
        BaseArtifactType artifact = findExistingArtifactByGAV(primaryGavInfo);
        SrampModelUtils.setCustomProperty(artifact, hashPropName, hashValue);

        // The meta-data has been updated in the local/temp archive - now send it to the remote repo
        artifactService.updateMetaData(artifact);
    }

    /**
     * Puts the artifact into the s-ramp repository.
     * @param gavInfo
     * @param req
     * @throws Exception
     */
    private void uploadArtifact(final MavenGavInfo gavInfo, HttpServletRequest req) throws Exception {
        BaseArtifactType artifact = findExistingArtifactByGAV(gavInfo);
        // If we found an artifact, we should update its content.  If not, we should upload
        // the artifact to the repository.
        if (artifact != null) {
            if (gavInfo.isSnapshot()) {
                artifactService.updateContent(ArtifactType.valueOf(artifact), artifact.getUuid(), gavInfo.getName(),
                        req.getInputStream());
                updateGavProperties(gavInfo, artifact);
            } else {
                throw new MavenRepositoryException(Messages.i18n.format("MAVEN_UPLOAD_ARTIFACT_EXISTS"));
            }
        } else {
            ArtifactType artifactType = getArtifactType(gavInfo, req);
            // Upload the content, then add the maven properties to the artifact
            // as meta-data
            artifact = artifactService.upload(artifactType, gavInfo.getName(), req.getInputStream());
            updateGavProperties(gavInfo, artifact);
        }
    }

    private void updateGavProperties(final MavenGavInfo gavInfo, BaseArtifactType artifact) throws Exception {
        SrampModelUtils.setCustomProperty(artifact, "maven.groupId", gavInfo.getGroupId());
        SrampModelUtils.setCustomProperty(artifact, "maven.artifactId", gavInfo.getArtifactId());
        SrampModelUtils.setCustomProperty(artifact, "maven.version", gavInfo.getVersion());
        artifact.setVersion(gavInfo.getVersion());
        if (gavInfo.getClassifier() != null) {
            SrampModelUtils.setCustomProperty(artifact, "maven.classifier", gavInfo.getClassifier());
        }
        if (gavInfo.getSnapshotId() != null && !gavInfo.getSnapshotId().equals("")) {
            SrampModelUtils.setCustomProperty(artifact, "maven.snapshot.id", gavInfo.getSnapshotId());
        }
        SrampModelUtils.setCustomProperty(artifact, "maven.type", gavInfo.getType());

        artifactService.updateMetaData(artifact);
    }

    /**
     * When looking up a hash, we have to lookup the primary artifact it's attached to.  Strip the hash extension
     * from the type, etc.
     *
     * @param req
     * @param hashExtensionLength
     * @return MavenGavInfo
     */
    private MavenGavInfo gavWithoutHash(HttpServletRequest req, int hashExtensionLength) {
        MavenGavInfo primaryGavInfo = MavenGavInfo.fromUrl(req.getRequestURI());
        primaryGavInfo.setType(
                primaryGavInfo.getType().substring(0, primaryGavInfo.getType().length() - hashExtensionLength));
        return primaryGavInfo;
    }

    /**
     * Generates a hash for the given content using the given hash algorithm.
     * @param string
     * @param hashAlgorithm
     */
    private String generateHash(String string, String hashAlgorithm) throws Exception {
        InputStream inputStream = IOUtils.toInputStream(string);
        MessageDigest md = MessageDigest.getInstance(hashAlgorithm);
        byte[] dataBytes = new byte[1024];

        int nread = 0;

        while ((nread = inputStream.read(dataBytes)) != -1) {
            md.update(dataBytes, 0, nread);
        }

        byte[] mdbytes = md.digest();

        // convert the byte to hex format
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < mdbytes.length; i++) {
            sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
        }

        return sb.toString();
    }

    /**
     * Gets the artifact type from the GAV or HttpServletRequest.
     * @param gavInfo
     */
    private ArtifactType getArtifactType(MavenGavInfo gavInfo, HttpServletRequest req) {
        String customAT = req.getParameter("artifactType");
        if (gavInfo.getType().equals("pom")) {
            return ArtifactType.valueOf("MavenPom", true);
        } else if (isPrimaryArtifact(gavInfo) && customAT != null) {
            return ArtifactType.valueOf(customAT, true);
        } else {
            return null;
        }
    }

    /**
     * Returns true if this represents the primary artifact in the Maven module.
     * @param gavInfo
     */
    private boolean isPrimaryArtifact(MavenGavInfo gavInfo) {
        return gavInfo.getClassifier() == null;
    }
}