org.overlord.sramp.wagon.SrampWagon.java Source code

Java tutorial

Introduction

Here is the source code for org.overlord.sramp.wagon.SrampWagon.java

Source

/*
 * Copyright 2012 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.wagon;

import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.xml.bind.JAXBException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.resource.Resource;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactEnum;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.BaseArtifactType;
import org.oasis_open.docs.s_ramp.ns.s_ramp_v1.ExtendedArtifactType;
import org.overlord.sramp.atom.archive.SrampArchive;
import org.overlord.sramp.atom.archive.SrampArchiveEntry;
import org.overlord.sramp.atom.archive.SrampArchiveException;
import org.overlord.sramp.atom.archive.expand.DefaultMetaDataFactory;
import org.overlord.sramp.atom.archive.expand.MetaDataProvider;
import org.overlord.sramp.atom.archive.expand.ZipToSrampArchive;
import org.overlord.sramp.atom.archive.expand.registry.ArchiveInfo;
import org.overlord.sramp.atom.archive.expand.registry.ZipToSrampArchiveRegistry;
import org.overlord.sramp.atom.err.SrampAtomException;
import org.overlord.sramp.client.SrampAtomApiClient;
import org.overlord.sramp.client.SrampClientException;
import org.overlord.sramp.client.SrampClientQuery;
import org.overlord.sramp.client.query.ArtifactSummary;
import org.overlord.sramp.client.query.QueryResultSet;
import org.overlord.sramp.common.ArtifactType;
import org.overlord.sramp.common.ArtifactTypeEnum;
import org.overlord.sramp.common.SrampModelUtils;
import org.overlord.sramp.wagon.i18n.Messages;
import org.overlord.sramp.wagon.models.MavenGavInfo;
import org.overlord.sramp.wagon.util.DevNullOutputStream;

/**
 * Implements a wagon provider that uses the S-RAMP Atom API.
 *
 * @author eric.wittmann@redhat.com
 */
@Component(role = Wagon.class, hint = "sramp", instantiationStrategy = "per-lookup")
public class SrampWagon extends StreamWagon {

    @Requirement
    private Logger logger;

    private transient SrampArchive archive;
    private transient SrampAtomApiClient client;

    /**
     * Constructor.
     */
    public SrampWagon() {
    }

    /**
     * @return the endpoint to use for the s-ramp repo
     */
    private String getSrampEndpoint() {
        String pomUrl = getRepository().getUrl();
        if (pomUrl.indexOf('?') > 0) {
            pomUrl = pomUrl.substring(0, pomUrl.indexOf('?'));
        }
        String replace = pomUrl.replace("sramp:", "http:").replace("sramps:", "https:"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
        return replace;
    }

    /**
     * @see org.apache.maven.wagon.AbstractWagon#openConnectionInternal()
     */
    @Override
    protected void openConnectionInternal() throws ConnectionException, AuthenticationException {
        // Even though the S-RAMP Atom API is session-less, use this open method
        // to start building up an S-RAMP archive containing the artifacts we are
        // storing in the repository (along with the meta-data for those artifacts).
        // The archive will serve as a temporary place to stash information we may
        // need later.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        try {
            // Create the archive
            this.archive = new SrampArchive();

            // Now create and configure the client.
            String endpoint = getSrampEndpoint();
            // Use sensible defaults
            String username = null;
            String password = null;
            AuthenticationInfo authInfo = this.getAuthenticationInfo();
            if (authInfo != null) {
                if (authInfo.getUserName() != null) {
                    username = authInfo.getUserName();
                }
                if (authInfo.getPassword() != null) {
                    password = authInfo.getPassword();
                }
            }
            if (username == null) {
                username = promptForUsername();
            }
            if (password == null) {
                password = promptForPassword();
            }

            this.client = new SrampAtomApiClient(endpoint, username, password, true);
        } catch (SrampArchiveException e) {
            throw new ConnectionException(Messages.i18n.format("FAILED_TO_CREATE_ARCHIVE"), e); //$NON-NLS-1$
        } catch (SrampClientException e) {
            throw new ConnectionException(Messages.i18n.format("FAILED_TO_CONNECT_TO_SRAMP"), e); //$NON-NLS-1$
        } catch (SrampAtomException e) {
            throw new ConnectionException(Messages.i18n.format("FAILED_TO_CONNECT_TO_SRAMP"), e); //$NON-NLS-1$
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
        }
    }

    /**
     * Prompts the user to enter a username for authentication credentials.
     */
    private String promptForUsername() {
        Console console = System.console();
        if (console != null) {
            return console.readLine(Messages.i18n.format("USERNAME_PROMPT")); //$NON-NLS-1$
        } else {
            System.err.println(Messages.i18n.format("NO_CONSOLE_ERROR_1")); //$NON-NLS-1$
            return null;
        }
    }

    /**
     * Prompts the user to enter a password for authentication credentials.
     */
    private String promptForPassword() {
        Console console = System.console();
        if (console != null) {
            return new String(console.readPassword(Messages.i18n.format("PASSWORD_PROMPT"))); //$NON-NLS-1$
        } else {
            System.err.println(Messages.i18n.format("NO_CONSOLE_ERROR_2")); //$NON-NLS-1$
            return null;
        }
    }

    /**
    * @see org.apache.maven.wagon.StreamWagon#closeConnection()
    */
    @Override
    public void closeConnection() throws ConnectionException {
        SrampArchive.closeQuietly(archive);
    }

    /**
     * @see org.apache.maven.wagon.StreamWagon#fillInputData(org.apache.maven.wagon.InputData)
     */
    @Override
    public void fillInputData(InputData inputData)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = inputData.getResource();
        MavenGavInfo gavInfo = MavenGavInfo.fromResource(resource);
        if (gavInfo.isMavenMetaData() && gavInfo.getVersion() == null) {
            doGenerateArtifactDirMavenMetaData(gavInfo, inputData);
            return;
        }

        if (gavInfo.isMavenMetaData() && gavInfo.getVersion() != null) {
            doGenerateSnapshotMavenMetaData(gavInfo, inputData);
            return;
        }

        debug(Messages.i18n.format("LOOKING_UP_RESOURCE_IN_SRAMP", resource)); //$NON-NLS-1$

        if (gavInfo.isHash()) {
            doGetHash(gavInfo, inputData);
        } else {
            doGetArtifact(gavInfo, inputData);
        }

    }

    /**
     * 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
      * @param inputData
     * @throws ResourceDoesNotExistException
      */
    private void doGenerateArtifactDirMavenMetaData(MavenGavInfo gavInfo, InputData inputData)
            throws ResourceDoesNotExistException {
        // See the comment in {@link SrampWagon#fillInputData(InputData)} about why we're doing this
        // context classloader magic.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        try {
            String artyPath = gavInfo.getFullName();
            if (gavInfo.isHash()) {
                artyPath = artyPath.substring(0, artyPath.lastIndexOf('.'));
            }
            SrampArchiveEntry entry = this.archive.getEntry(artyPath);
            if (entry == null) {
                QueryResultSet resultSet = client
                        .buildQuery("/s-ramp[@maven.groupId = ? and @maven.artifactId = ?]") //$NON-NLS-1$
                        .parameter(gavInfo.getGroupId()).parameter(gavInfo.getArtifactId())
                        .propertyName("maven.version") //$NON-NLS-1$
                        .count(500).orderBy("createdTimestamp").ascending().query(); //$NON-NLS-1$
                if (resultSet.size() == 0) {
                    throw new Exception(Messages.i18n.format("NO_ARTIFACTS_FOUND")); //$NON-NLS-1$
                }

                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"); //$NON-NLS-1$
                for (ArtifactSummary artifactSummary : resultSet) {
                    String version = artifactSummary.getCustomPropertyValue("maven.version"); //$NON-NLS-1$
                    if (versions.add(version)) {
                        latest = version;
                        if (!version.endsWith("-SNAPSHOT")) { //$NON-NLS-1$
                            release = version;
                        }
                    }
                    lastUpdated = format.format(artifactSummary.getCreatedTimestamp());
                }

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

                BaseArtifactType artifact = ArtifactType.ExtendedDocument("MavenMetaData").newArtifactInstance(); //$NON-NLS-1$
                this.archive.addEntry(artyPath, artifact, IOUtils.toInputStream(mavenMetadata.toString()));

                entry = this.archive.getEntry(artyPath);
            }

            if (!gavInfo.isHash()) {
                inputData.setInputStream(this.archive.getInputStream(entry));
            } else {
                String hash = generateHash(this.archive.getInputStream(entry), gavInfo.getHashAlgorithm());
                inputData.setInputStream(IOUtils.toInputStream(hash));
            }
        } catch (Exception e) {
            throw new ResourceDoesNotExistException(Messages.i18n.format("FAILED_TO_GENERATE_METADATA"), e); //$NON-NLS-1$
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
        }
    }

    /**
     * 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
     * @param inputData
     * @throws ResourceDoesNotExistException
     */
    private void doGenerateSnapshotMavenMetaData(MavenGavInfo gavInfo, InputData inputData)
            throws ResourceDoesNotExistException {
        // See the comment in {@link SrampWagon#fillInputData(InputData)} about why we're doing this
        // context classloader magic.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        try {
            String artyPath = gavInfo.getFullName();
            if (gavInfo.isHash()) {
                artyPath = artyPath.substring(0, artyPath.lastIndexOf('.'));
            }
            SrampArchiveEntry entry = this.archive.getEntry(artyPath);
            if (entry == null) {
                QueryResultSet resultSet = client
                        .buildQuery("/s-ramp[@maven.groupId = ? and @maven.artifactId = ? and @maven.version = ?]") //$NON-NLS-1$
                        .parameter(gavInfo.getGroupId()).parameter(gavInfo.getArtifactId())
                        .parameter(gavInfo.getVersion()).propertyName("maven.classifier").propertyName("maven.type") //$NON-NLS-1$ //$NON-NLS-2$
                        .count(500).orderBy("createdTimestamp").ascending().query(); //$NON-NLS-1$
                if (resultSet.size() == 0) {
                    throw new Exception(Messages.i18n.format("NO_ARTIFACTS_FOUND")); //$NON-NLS-1$
                }

                SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss"); //$NON-NLS-1$
                SimpleDateFormat updatedFormat = new SimpleDateFormat("yyyyMMddHHmmss"); //$NON-NLS-1$

                StringBuilder snapshotVersions = new StringBuilder();
                snapshotVersions.append("    <snapshotVersions>\n"); //$NON-NLS-1$
                Set<String> processed = new HashSet<String>();
                Date latestDate = null;
                for (ArtifactSummary artifactSummary : resultSet) {
                    String extension = artifactSummary.getCustomPropertyValue("maven.type"); //$NON-NLS-1$
                    String classifier = artifactSummary.getCustomPropertyValue("maven.classifier"); //$NON-NLS-1$
                    String value = gavInfo.getVersion();
                    Date updatedDate = artifactSummary.getLastModifiedTimestamp();
                    String updated = updatedFormat.format(updatedDate);
                    String pkey = classifier + "::" + extension; //$NON-NLS-1$
                    if (processed.add(pkey)) {
                        snapshotVersions.append("      <snapshotVersion>\n"); //$NON-NLS-1$
                        if (classifier != null)
                            snapshotVersions.append("        <classifier>").append(classifier) //$NON-NLS-1$
                                    .append("</classifier>\n"); //$NON-NLS-1$
                        snapshotVersions.append("        <extension>").append(extension).append("</extension>\n"); //$NON-NLS-1$ //$NON-NLS-2$
                        snapshotVersions.append("        <value>").append(value).append("</value>\n"); //$NON-NLS-1$ //$NON-NLS-2$
                        snapshotVersions.append("        <updated>").append(updated).append("</updated>\n"); //$NON-NLS-1$ //$NON-NLS-2$
                        snapshotVersions.append("      </snapshotVersion>\n"); //$NON-NLS-1$
                        if (latestDate == null || latestDate.before(updatedDate)) {
                            latestDate = updatedDate;
                        }
                    }
                }
                snapshotVersions.append("    </snapshotVersions>\n"); //$NON-NLS-1$

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

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

                BaseArtifactType artifact = ArtifactType.ExtendedDocument("MavenMetaData").newArtifactInstance(); //$NON-NLS-1$
                this.archive.addEntry(artyPath, artifact, IOUtils.toInputStream(mavenMetadata.toString()));

                entry = this.archive.getEntry(artyPath);
            }

            if (!gavInfo.isHash()) {
                inputData.setInputStream(this.archive.getInputStream(entry));
            } else {
                String hash = generateHash(this.archive.getInputStream(entry), gavInfo.getHashAlgorithm());
                inputData.setInputStream(IOUtils.toInputStream(hash));
            }
        } catch (Exception e) {
            throw new ResourceDoesNotExistException(Messages.i18n.format("FAILED_TO_GENERATE_METADATA"), e); //$NON-NLS-1$
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
        }
    }

    /**
    * Gets the hash data from the s-ramp repository and stores it in the {@link InputData} for
    * use by Maven.
    * @param gavInfo
    * @param inputData
    * @throws TransferFailedException
    * @throws ResourceDoesNotExistException
    * @throws AuthorizationException
    */
    private void doGetHash(MavenGavInfo gavInfo, InputData inputData)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        String artyPath = gavInfo.getFullName();
        String hashPropName;
        if (gavInfo.getType().endsWith(".md5")) { //$NON-NLS-1$
            hashPropName = "maven.hash.md5"; //$NON-NLS-1$
            artyPath = artyPath.substring(0, artyPath.length() - 4);
        } else {
            hashPropName = "maven.hash.sha1"; //$NON-NLS-1$
            artyPath = artyPath.substring(0, artyPath.length() - 5);
        }
        // See the comment in {@link SrampWagon#fillInputData(InputData)} about why we're doing this
        // context classloader magic.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        try {
            SrampArchiveEntry entry = this.archive.getEntry(artyPath);
            if (entry == null) {
                throw new ResourceDoesNotExistException(
                        Messages.i18n.format("MISSING_RESOURCE_HASH", gavInfo.getName())); //$NON-NLS-1$
            }
            BaseArtifactType metaData = entry.getMetaData();

            String hashValue = SrampModelUtils.getCustomProperty(metaData, hashPropName);
            if (hashValue == null) {
                throw new ResourceDoesNotExistException(
                        Messages.i18n.format("MISSING_RESOURCE_HASH", gavInfo.getName())); //$NON-NLS-1$
            }
            inputData.setInputStream(IOUtils.toInputStream(hashValue));
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
        }
    }

    /***
     * Gets the artifact content from the s-ramp repository and stores it in the {@link InputData}
     * object for use by Maven.
     * @param gavInfo
     * @param inputData
     * @throws TransferFailedException
     * @throws ResourceDoesNotExistException
     * @throws AuthorizationException
     */
    private void doGetArtifact(MavenGavInfo gavInfo, InputData inputData)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        // RESTEasy uses the current thread's context classloader to load its logger class.  This
        // fails in Maven because the context classloader is the wagon plugin's classloader, which
        // doesn't know about any of the RESTEasy JARs.  So here we're temporarily setting the
        // context classloader to the s-ramp wagon extension's classloader, which should have access
        // to all the right stuff.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        try {
            // Query the artifact meta data using GAV info
            BaseArtifactType artifact = findExistingArtifact(client, gavInfo);
            if (artifact == null)
                throw new ResourceDoesNotExistException(
                        Messages.i18n.format("ARTIFACT_NOT_FOUND", gavInfo.getName())); //$NON-NLS-1$
            this.archive.addEntry(gavInfo.getFullName(), artifact, null);
            ArtifactType type = ArtifactType.valueOf(artifact);

            // Get the artifact content as an input stream
            InputStream artifactContent = client.getArtifactContent(type, artifact.getUuid());
            inputData.setInputStream(artifactContent);
        } catch (ResourceDoesNotExistException e) {
            throw e;
        } catch (SrampClientException e) {
            if (e.getCause() instanceof HttpHostConnectException) {
                this.debug(Messages.i18n.format("SRAMP_CONNECTION_FAILED", e.getMessage())); //$NON-NLS-1$
            } else {
                this.error(e.getMessage(), e);
            }
            throw new ResourceDoesNotExistException(
                    Messages.i18n.format("FAILED_TO_GET_RESOURCE_FROM_SRAMP", gavInfo.getName())); //$NON-NLS-1$
        } catch (Throwable t) {
            this.error(t.getMessage(), t);
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
        }
    }

    /**
    * @see org.apache.maven.wagon.StreamWagon#putFromStream(java.io.InputStream, java.lang.String)
    */
    @Override
    public void putFromStream(InputStream stream, String destination)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = new Resource(destination);
        putCommon(resource, null, stream);
    }

    /**
     * @see org.apache.maven.wagon.StreamWagon#putFromStream(java.io.InputStream, java.lang.String, long, long)
     */
    @Override
    public void putFromStream(InputStream stream, String destination, long contentLength, long lastModified)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        Resource resource = new Resource(destination);
        resource.setContentLength(contentLength);
        resource.setLastModified(lastModified);
        putCommon(resource, null, stream);
    }

    /**
     * @see org.apache.maven.wagon.StreamWagon#put(java.io.File, java.lang.String)
     */
    @Override
    public void put(File source, String resourceName)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        InputStream resourceInputStream = null;
        try {
            resourceInputStream = new FileInputStream(source);
        } catch (FileNotFoundException e) {
            throw new TransferFailedException(e.getMessage());
        }

        Resource resource = new Resource(resourceName);
        resource.setContentLength(source.length());
        resource.setLastModified(source.lastModified());
        putCommon(resource, source, resourceInputStream);
    }

    /**
     * Common put implementation.  Handles firing events and ultimately sending the data via the
     * s-ramp client.
     * @param resource
     * @param source
     * @param content
     * @throws TransferFailedException
     * @throws ResourceDoesNotExistException
     * @throws AuthorizationException
     */
    private void putCommon(Resource resource, File source, InputStream content)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
        logger.info(Messages.i18n.format("UPLOADING_TO_SRAMP", resource.getName())); //$NON-NLS-1$
        firePutInitiated(resource, source);

        firePutStarted(resource, source);
        if (resource.getName().contains("maven-metadata.xml")) { //$NON-NLS-1$
            logger.info(Messages.i18n.format("SKIPPING_ARTY", resource.getName())); //$NON-NLS-1$
            try {
                transfer(resource, content, new DevNullOutputStream(), TransferEvent.REQUEST_PUT);
            } catch (IOException e) {
                throw new TransferFailedException(e.getMessage(), e);
            }
        } else {
            doPut(resource, content);
        }
        firePutCompleted(resource, source);
    }

    /**
     * Gets the artifact type from the resource.
     * @param gavInfo
     */
    private ArtifactType getArtifactType(MavenGavInfo gavInfo, String artifactModel) {
        String customAT = getParamFromRepositoryUrl("artifactType"); //$NON-NLS-1$
        if (gavInfo.getType().equals("pom")) { //$NON-NLS-1$
            return ArtifactType.valueOf("MavenPom"); //$NON-NLS-1$
        } else if (isPrimaryArtifact(gavInfo) && customAT != null) {
            return ArtifactType.valueOf(customAT);
        }
        if (artifactModel != null) {
            return ArtifactType.valueOf("ext", artifactModel, true); //$NON-NLS-1$
        } else {
            return ArtifactType.valueOf(ArtifactTypeEnum.Document.name());
        }
    }

    /**
     * Puts the maven resource into the s-ramp repository.
     * @param resource
     * @param resourceInputStream
     * @throws TransferFailedException
     */
    private void doPut(Resource resource, InputStream resourceInputStream) throws TransferFailedException {
        MavenGavInfo gavInfo = MavenGavInfo.fromResource(resource);
        if (gavInfo.isHash()) {
            doPutHash(gavInfo, resourceInputStream);
        } else {
            doPutArtifact(gavInfo, resourceInputStream);
        }
    }

    /**
     * Updates an artifact by storing its hash value as an S-RAMP property.
     * @param gavInfo
     * @param resourceInputStream
     * @throws TransferFailedException
     */
    private void doPutHash(MavenGavInfo gavInfo, InputStream resourceInputStream) throws TransferFailedException {
        logger.info(Messages.i18n.format("STORING_HASH_AS_PROP", gavInfo.getName())); //$NON-NLS-1$
        try {
            String artyPath = gavInfo.getFullName();
            String hashPropName;
            if (gavInfo.getType().endsWith(".md5")) { //$NON-NLS-1$
                hashPropName = "maven.hash.md5"; //$NON-NLS-1$
                artyPath = artyPath.substring(0, artyPath.length() - 4);
            } else {
                hashPropName = "maven.hash.sha1"; //$NON-NLS-1$
                artyPath = artyPath.substring(0, artyPath.length() - 5);
            }
            String hashValue = IOUtils.toString(resourceInputStream);

            // See the comment in {@link SrampWagon#fillInputData(InputData)} about why we're doing this
            // context classloader magic.
            ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
            try {
                SrampArchiveEntry entry = this.archive.getEntry(artyPath);
                // Re-fetch the artifact meta-data in case it changed on the server since we uploaded it.
                BaseArtifactType metaData = client.getArtifactMetaData(entry.getMetaData().getUuid());
                SrampModelUtils.setCustomProperty(metaData, hashPropName, hashValue);
                this.archive.updateEntry(entry, null);

                // The meta-data has been updated in the local/temp archive - now send it to the remote repo
                client.updateArtifactMetaData(metaData);
            } catch (Throwable t) {
                throw new TransferFailedException(t.getMessage(), t);
            } finally {
                Thread.currentThread().setContextClassLoader(oldCtxCL);
            }
        } catch (Exception e) {
            throw new TransferFailedException(Messages.i18n.format("FAILED_TO_STORE_HASH", gavInfo.getName()), e); //$NON-NLS-1$
        }
    }

    /**
     * Puts the artifact into the s-ramp repository.
     * @param gavInfo
     * @param resourceInputStream
     * @throws TransferFailedException
     */
    private void doPutArtifact(final MavenGavInfo gavInfo, InputStream resourceInputStream)
            throws TransferFailedException {

        // See the comment in {@link SrampWagon#fillInputData(InputData)} about why we're doing this
        // context classloader magic.
        ClassLoader oldCtxCL = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(SrampWagon.class.getClassLoader());
        File tempResourceFile = null;
        ZipToSrampArchive expander = null;
        SrampArchive archive = null;
        BaseArtifactType artifactGrouping = null;
        try {
            // First, stash the content in a temp file - we may need it multiple times.
            tempResourceFile = stashResourceContent(resourceInputStream);
            resourceInputStream = FileUtils.openInputStream(tempResourceFile);

            ArchiveInfo archiveInfo = ZipToSrampArchiveRegistry.inspectArchive(resourceInputStream);
            ArtifactType artifactType = getArtifactType(gavInfo, archiveInfo.type);

            resourceInputStream = FileUtils.openInputStream(tempResourceFile);

            // Is the artifact grouping option enabled?
            if (isPrimaryArtifact(gavInfo) && getParamFromRepositoryUrl("artifactGrouping") != null) { //$NON-NLS-1$
                artifactGrouping = ensureArtifactGrouping();
            }

            // Only search for existing artifacts by GAV info here
            BaseArtifactType artifact = findExistingArtifactByGAV(client, gavInfo);
            // If we found an artifact, we should update its content.  If not, we should upload
            // the artifact to the repository.
            if (artifact != null) {
                this.archive.addEntry(gavInfo.getFullName(), artifact, null);
                client.updateArtifactContent(artifact, resourceInputStream);
                if (ZipToSrampArchiveRegistry.canExpand(artifactType)) {
                    final String parentUUID = artifact.getUuid();
                    cleanExpandedArtifacts(client, parentUUID);
                }
            } else {
                // Upload the content, then add the maven properties to the artifact
                // as meta-data
                artifact = client.uploadArtifact(artifactType, resourceInputStream, gavInfo.getName());
                SrampModelUtils.setCustomProperty(artifact, "maven.groupId", gavInfo.getGroupId()); //$NON-NLS-1$
                SrampModelUtils.setCustomProperty(artifact, "maven.artifactId", gavInfo.getArtifactId()); //$NON-NLS-1$
                SrampModelUtils.setCustomProperty(artifact, "maven.version", gavInfo.getVersion()); //$NON-NLS-1$
                artifact.setVersion(gavInfo.getVersion());
                if (gavInfo.getClassifier() != null)
                    SrampModelUtils.setCustomProperty(artifact, "maven.classifier", gavInfo.getClassifier()); //$NON-NLS-1$
                SrampModelUtils.setCustomProperty(artifact, "maven.type", gavInfo.getType()); //$NON-NLS-1$
                // Also create a relationship to the artifact grouping, if necessary
                if (artifactGrouping != null) {
                    SrampModelUtils.addGenericRelationship(artifact, "groupedBy", artifactGrouping.getUuid()); //$NON-NLS-1$
                    SrampModelUtils.addGenericRelationship(artifactGrouping, "groups", artifact.getUuid()); //$NON-NLS-1$
                    client.updateArtifactMetaData(artifactGrouping);
                }

                client.updateArtifactMetaData(artifact);
                this.archive.addEntry(gavInfo.getFullName(), artifact, null);
            }

            // Now also add "expanded" content to the s-ramp repository
            expander = ZipToSrampArchiveRegistry.createExpander(artifactType, tempResourceFile);
            if (expander != null) {
                expander.setContextParam(DefaultMetaDataFactory.PARENT_UUID, artifact.getUuid());
                expander.addMetaDataProvider(new MetaDataProvider() {
                    @Override
                    public void provideMetaData(BaseArtifactType artifact) {
                        SrampModelUtils.setCustomProperty(artifact, "maven.parent-groupId", gavInfo.getGroupId()); //$NON-NLS-1$
                        SrampModelUtils.setCustomProperty(artifact, "maven.parent-artifactId", //$NON-NLS-1$
                                gavInfo.getArtifactId());
                        SrampModelUtils.setCustomProperty(artifact, "maven.parent-version", gavInfo.getVersion()); //$NON-NLS-1$
                        SrampModelUtils.setCustomProperty(artifact, "maven.parent-type", gavInfo.getType()); //$NON-NLS-1$
                    }
                });
                archive = expander.createSrampArchive();
                client.uploadBatch(archive);
            }
        } catch (Throwable t) {
            throw new TransferFailedException(t.getMessage(), t);
        } finally {
            Thread.currentThread().setContextClassLoader(oldCtxCL);
            SrampArchive.closeQuietly(archive);
            ZipToSrampArchive.closeQuietly(expander);
            FileUtils.deleteQuietly(tempResourceFile);
        }
    }

    /**
     * Ensures that the required ArtifactGrouping is present in the repository.
    * @throws SrampAtomException
    * @throws SrampClientException
     */
    private BaseArtifactType ensureArtifactGrouping() throws SrampClientException, SrampAtomException {
        String groupingName = getParamFromRepositoryUrl("artifactGrouping"); //$NON-NLS-1$
        if (groupingName == null || groupingName.trim().length() == 0) {
            logger.warn(Messages.i18n.format("NO_ARTIFACT_GROUPING_NAME")); //$NON-NLS-1$
            return null;
        }
        QueryResultSet query = client.buildQuery("/s-ramp/ext/ArtifactGrouping[@name = ?]").parameter(groupingName) //$NON-NLS-1$
                .count(2).query();
        if (query.size() > 1) {
            logger.warn(Messages.i18n.format("MULTIPLE_ARTIFACT_GROUPSING_FOUND", groupingName)); //$NON-NLS-1$
            return null;
        } else if (query.size() == 1) {
            ArtifactSummary summary = query.get(0);
            return client.getArtifactMetaData(summary.getType(), summary.getUuid());
        } else {
            ExtendedArtifactType groupingArtifact = new ExtendedArtifactType();
            groupingArtifact.setArtifactType(BaseArtifactEnum.EXTENDED_ARTIFACT_TYPE);
            groupingArtifact.setExtendedType("ArtifactGrouping"); //$NON-NLS-1$
            groupingArtifact.setName(groupingName);
            groupingArtifact.setDescription(Messages.i18n.format("ARTIFACT_GROUPING_DESCRIPTION")); //$NON-NLS-1$
            return client.createArtifact(groupingArtifact);
        }
    }

    /**
    * Deletes the 'expanded' artifacts from the s-ramp repository.
    * @param client
    * @param parentUUID
    * @throws SrampClientException
    * @throws SrampAtomException
    */
    private void cleanExpandedArtifacts(SrampAtomApiClient client, String parentUUID)
            throws SrampAtomException, SrampClientException {
        String query = String.format("/s-ramp[mavenParent[@uuid = '%1$s']]", parentUUID); //$NON-NLS-1$
        boolean done = false;
        while (!done) {
            QueryResultSet rset = client.query(query, 0, 20, "name", true); //$NON-NLS-1$
            if (rset.size() == 0) {
                done = true;
            } else {
                for (ArtifactSummary entry : rset) {
                    ArtifactType artifactType = entry.getType();
                    String uuid = entry.getUuid();
                    client.deleteArtifact(uuid, artifactType);
                }
            }
        }
    }

    /**
     * Make a temporary copy of the resource by saving the content to a temp file.
     * @param resourceInputStream
     * @throws IOException
     */
    private File stashResourceContent(InputStream resourceInputStream) throws IOException {
        File resourceTempFile = null;
        OutputStream oStream = null;
        try {
            resourceTempFile = File.createTempFile("s-ramp-wagon-resource", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
            oStream = FileUtils.openOutputStream(resourceTempFile);
        } finally {
            IOUtils.copy(resourceInputStream, oStream);
            IOUtils.closeQuietly(resourceInputStream);
            IOUtils.closeQuietly(oStream);
        }
        return resourceTempFile;
    }

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

    /**
     * Finds an existing artifact in the s-ramp repository that matches the GAV information.
     * @param client
     * @param gavInfo
     * @return an s-ramp artifact (if found) or null (if not found)
     * @throws SrampClientException
     * @throws SrampAtomException
     * @throws JAXBException
     */
    private BaseArtifactType findExistingArtifactByGAV(SrampAtomApiClient client, MavenGavInfo gavInfo)
            throws SrampAtomException, SrampClientException, JAXBException {
        SrampClientQuery clientQuery = null;
        // Search by classifier if we have one...
        if (gavInfo.getClassifier() == null) {
            clientQuery = client.buildQuery(
                    "/s-ramp[@maven.groupId = ? and @maven.artifactId = ? and @maven.version = ? and @maven.type = ?]") //$NON-NLS-1$
                    .parameter(gavInfo.getGroupId()).parameter(gavInfo.getArtifactId())
                    .parameter(gavInfo.getVersion()).parameter(gavInfo.getType());
        } else {
            clientQuery = client.buildQuery(
                    "/s-ramp[@maven.groupId = ? and @maven.artifactId = ? and @maven.version = ? and @maven.classifier = ? and @maven.type = ?]") //$NON-NLS-1$
                    .parameter(gavInfo.getGroupId()).parameter(gavInfo.getArtifactId())
                    .parameter(gavInfo.getVersion()).parameter(gavInfo.getClassifier())
                    .parameter(gavInfo.getType());
        }
        QueryResultSet rset = clientQuery.count(100).query();
        if (rset.size() > 0) {
            for (ArtifactSummary summary : rset) {
                String uuid = summary.getUuid();
                ArtifactType artifactType = summary.getType();
                BaseArtifactType arty = client.getArtifactMetaData(artifactType, uuid);
                // 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(arty, "maven.classifier"); //$NON-NLS-1$
                    if (artyClassifier == null) {
                        return arty;
                    }
                } else {
                    // If classifier was supplied in the GAV info, we'll get the first artifact <shrug>
                    return arty;
                }
            }
        }
        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 client
     * @param artifactType
     * @param gavInfo
     * @return an existing s-ramp artifact (if found) or null (if not found)
     * @throws SrampClientException
     * @throws SrampAtomException
     * @throws JAXBException
     */
    private BaseArtifactType findExistingArtifactByUniversal(SrampAtomApiClient client, MavenGavInfo gavInfo)
            throws SrampAtomException, SrampClientException, JAXBException {
        String artifactType = gavInfo.getGroupId().substring(gavInfo.getGroupId().indexOf('.') + 1);
        String uuid = gavInfo.getArtifactId();
        try {
            return client.getArtifactMetaData(ArtifactType.valueOf(artifactType), uuid);
        } catch (Throwable t) {
            debug(t.getMessage());
        }
        return null;
    }

    /**
     * @see org.apache.maven.wagon.StreamWagon#fillOutputData(org.apache.maven.wagon.OutputData)
     */
    @Override
    public void fillOutputData(OutputData outputData) throws TransferFailedException {
        // Since the wagon is implementing the put method directly, the StreamWagon's
        // implementation is never called.
        throw new RuntimeException("Should never get here!"); //$NON-NLS-1$
    }

    /**
     * Gets a URL parameter by name from the repository URL.
     * @param paramName
     */
    protected String getParamFromRepositoryUrl(String paramName) {
        String url = getRepository().getUrl();
        int idx = url.indexOf('?');
        if (idx == -1)
            return null;
        String query = url.substring(idx + 1);
        String[] params = query.split("&"); //$NON-NLS-1$
        for (String paramPair : params) {
            String[] pp = paramPair.split("="); //$NON-NLS-1$
            if (pp.length == 2) {
                String key = pp[0];
                String val = pp[1];
                if (key.equals(paramName)) {
                    return val;
                }
            } else {
                throw new RuntimeException(Messages.i18n.format("INVALID_QUERY_PARAM")); //$NON-NLS-1$
            }
        }
        return null;
    }

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

    /**
     * Generates a hash for the given content using the given hash algorithm.
     * @param inputStream
     * @param hashAlgorithm
     */
    private String generateHash(InputStream inputStream, String hashAlgorithm) throws Exception {
        try {
            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();
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * @param message
     */
    private void debug(String message) {
        if (logger != null)
            logger.debug(message);
    }

    /**
     * @param message
     * @param t
     */
    private void error(String message, Throwable t) {
        if (logger != null)
            logger.error(message, t);
    }

}