/*
* 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.artificer.server.mvn.services;
import org.apache.commons.io.IOUtils;
import org.artificer.common.ArtifactType;
import org.artificer.common.ArtificerConfig;
import org.artificer.common.ArtificerModelUtils;
import org.artificer.common.maven.MavenGavInfo;
import org.artificer.common.maven.MavenUtil;
import org.artificer.common.query.ArtifactSummary;
import org.artificer.repository.query.PagedResult;
import org.artificer.server.ArtifactServiceImpl;
import org.artificer.server.QueryServiceImpl;
import org.artificer.server.i18n.Messages;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactType;
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.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 = ArtificerConfig.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 {
PagedResult<ArtifactSummary> artifacts = queryService.query(
"/s-ramp[@maven.groupId = '" + gavInfo.getGroupId() + "' and @maven.artifactId = '" + gavInfo.getArtifactId() + "']",
"createdTimestamp", true);
if (artifacts.getTotalSize() == 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 (ArtifactSummary artifactSummary : artifacts.getResults()) {
BaseArtifactType artifact = artifactService.getMetaData(
artifactSummary.getModel(), artifactSummary.getType(), artifactSummary.getUuid());
String version = ArtificerModelUtils.getCustomProperty(artifact, "maven.version");
if (versions.add(version)) {
latest = version;
if (!version.endsWith("-SNAPSHOT")) {
release = version;
}
}
lastUpdated = format.format(artifactSummary.getCreatedTimestamp().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 {
PagedResult<ArtifactSummary> artifacts = queryService.query(
"/s-ramp[@maven.groupId = '" + gavInfo.getGroupId() + "'" +
" and @maven.artifactId = '" + gavInfo.getArtifactId() + "'" +
" and @maven.version = '" + gavInfo.getVersion() + "']",
"createdTimestamp", true);
if (artifacts.getTotalSize() == 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 (ArtifactSummary artifactSummary : artifacts.getResults()) {
BaseArtifactType artifact = artifactService.getMetaData(
artifactSummary.getModel(), artifactSummary.getType(), artifactSummary.getUuid());
String extension = ArtificerModelUtils.getCustomProperty(artifact, "maven.type");
String classifier = ArtificerModelUtils.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 ArtificerModelUtils.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);
PagedResult<ArtifactSummary> artifacts = queryService.query(query, "createdTimestamp", false);
if (artifacts.getTotalSize() > 0) {
for (ArtifactSummary artifactSummary : artifacts.getResults()) {
BaseArtifactType artifact = artifactService.getMetaData(
artifactSummary.getModel(), artifactSummary.getType(), artifactSummary.getUuid());
// 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 = ArtificerModelUtils.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);
ArtificerModelUtils.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);
ArtifactType artifactType = getArtifactType(gavInfo, req);
// 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.delete(artifactType, artifact.getUuid());
artifact = artifactService.upload(artifactType, gavInfo.getName(), req.getInputStream());
updateGavProperties(gavInfo, artifact);
} else {
throw new MavenRepositoryException(Messages.i18n.format("MAVEN_UPLOAD_ARTIFACT_EXISTS"));
}
} else {
artifact = artifactService.upload(artifactType, gavInfo.getName(), req.getInputStream());
updateGavProperties(gavInfo, artifact);
}
}
private void updateGavProperties(final MavenGavInfo gavInfo, BaseArtifactType artifact) throws Exception {
ArtificerModelUtils.setCustomProperty(artifact, "maven.groupId", gavInfo.getGroupId());
ArtificerModelUtils.setCustomProperty(artifact, "maven.artifactId", gavInfo.getArtifactId());
ArtificerModelUtils.setCustomProperty(artifact, "maven.version", gavInfo.getVersion());
artifact.setVersion(gavInfo.getVersion());
if (gavInfo.getClassifier() != null) {
ArtificerModelUtils.setCustomProperty(artifact, "maven.classifier", gavInfo.getClassifier());
}
if (gavInfo.getSnapshotId() != null && !gavInfo.getSnapshotId().equals("")) {
ArtificerModelUtils.setCustomProperty(artifact, "maven.snapshot.id", gavInfo.getSnapshotId());
}
ArtificerModelUtils.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;
}
}