/**
* Copyright (c) 2008-2011 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://www.sonatype.com/products/nexus/attributions.
*
* This program is free software: you can redistribute it and/or modify it only under the terms of the GNU Affero General
* Public License Version 3 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License Version 3
* for more details.
*
* You should have received a copy of the GNU Affero General Public License Version 3 along with this program. If not, see
* http://www.gnu.org/licenses.
*
* Sonatype Nexus (TM) Open Source Version is available from Sonatype, Inc. Sonatype and Sonatype Nexus are trademarks of
* Sonatype, Inc. Apache Maven is a trademark of the Apache Foundation. M2Eclipse is a trademark of the Eclipse Foundation.
* All other trademarks are the property of their respective owners.
*/
package org.sonatype.nexus.rest.artifact;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import org.apache.commons.fileupload.FileItem;
import org.apache.maven.index.artifact.Gav;
import org.apache.maven.index.artifact.VersionUtils;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.shiro.subject.Subject;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.restlet.Context;
import org.restlet.data.Form;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.sonatype.nexus.proxy.AccessDeniedException;
import org.sonatype.nexus.proxy.IllegalOperationException;
import org.sonatype.nexus.proxy.IllegalRequestException;
import org.sonatype.nexus.proxy.ItemNotFoundException;
import org.sonatype.nexus.proxy.NoSuchRepositoryException;
import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
import org.sonatype.nexus.proxy.RepositoryNotAvailableException;
import org.sonatype.nexus.proxy.StorageException;
import org.sonatype.nexus.proxy.access.AccessManager;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.nexus.proxy.maven.ArtifactStoreHelper;
import org.sonatype.nexus.proxy.maven.ArtifactStoreRequest;
import org.sonatype.nexus.proxy.maven.MavenRepository;
import org.sonatype.nexus.proxy.maven.RepositoryPolicy;
import org.sonatype.nexus.proxy.repository.Repository;
import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
import org.sonatype.nexus.rest.AbstractNexusPlexusResource;
import org.sonatype.nexus.rest.AbstractResourceStoreContentPlexusResource;
import org.sonatype.nexus.rest.StorageFileItemRepresentation;
import org.sonatype.nexus.rest.model.ArtifactCoordinate;
import org.sonatype.security.SecuritySystem;
public abstract class AbstractArtifactPlexusResource
extends AbstractNexusPlexusResource
{
@Requirement
private SecuritySystem securitySystem;
/**
* Centralized way to create ResourceStoreRequests, since we have to fill in various things in Request context, like
* authenticated username, etc.
*
* @param isLocal
* @return
*/
protected ArtifactStoreRequest getResourceStoreRequest( Request request, boolean localOnly, boolean remoteOnly,
String repositoryId, String g, String a, String v,
String p, String c, String e )
throws ResourceException
{
if ( StringUtils.isBlank( p ) && StringUtils.isBlank( e ) )
{
// if packaging and extension is both blank, it is a bad request
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST,
"Deployment tried with both 'packaging' and/or 'extension' being empty! One of these values is mandatory!" );
}
MavenRepository mavenRepository = getMavenRepository( repositoryId );
// if extension is not given, fall-back to packaging and apply mapper
if ( StringUtils.isBlank( e ) )
{
e = mavenRepository.getArtifactPackagingMapper().getExtensionForPackaging( p );
}
// clean up the classifier
if ( StringUtils.isBlank( c ) )
{
c = null;
}
Gav gav = new Gav( g, a, v, c, e, null, null, null, false, null, false, null );
ArtifactStoreRequest result = new ArtifactStoreRequest( mavenRepository, gav, localOnly, remoteOnly );
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Created ArtifactStoreRequest request for " + result.getRequestPath() );
}
// stuff in the originating remote address
result.getRequestContext().put( AccessManager.REQUEST_REMOTE_ADDRESS, getValidRemoteIPAddress( request ) );
// stuff in the user id if we have it in request
Subject subject = securitySystem.getSubject();
if ( subject != null && subject.getPrincipal() != null )
{
result.getRequestContext().put( AccessManager.REQUEST_USER, subject.getPrincipal().toString() );
}
// this is HTTPS, get the cert and stuff it too for later
if ( request.isConfidential() )
{
result.getRequestContext().put( AccessManager.REQUEST_CONFIDENTIAL, Boolean.TRUE );
List<?> certs = (List<?>) request.getAttributes().get( "org.restlet.https.clientCertificates" );
if ( certs != null )
{
result.getRequestContext().put( AccessManager.REQUEST_CERTIFICATES, certs );
}
}
// put the incoming URLs
result.setRequestAppRootUrl( getContextRoot( request ).toString() );
result.setRequestUrl( request.getOriginalRef().toString() );
return result;
}
protected Model getPom( Variant variant, Request request, Response response )
throws ResourceException
{
Form form = request.getResourceRef().getQueryAsForm();
// TODO: enable only one section retrieval of POM, ie. only mailing lists, or team members
String groupId = form.getFirstValue( "g" );
String artifactId = form.getFirstValue( "a" );
String version = form.getFirstValue( "v" );
String repositoryId = form.getFirstValue( "r" );
if ( groupId == null || artifactId == null || version == null || repositoryId == null )
{
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST );
}
ArtifactStoreRequest gavRequest =
getResourceStoreRequest( request, false, false, repositoryId, groupId, artifactId, version, null, null,
"pom" );
gavRequest.setRequestLocalOnly( isLocal( request, gavRequest.getRequestPath() ) );
try
{
MavenRepository mavenRepository = getMavenRepository( repositoryId );
ArtifactStoreHelper helper = mavenRepository.getArtifactStoreHelper();
InputStream pomContent = null;
InputStreamReader ir = null;
Model pom = null;
try
{
StorageFileItem file = helper.retrieveArtifactPom( gavRequest );
pomContent = file.getInputStream();
MavenXpp3Reader reader = new MavenXpp3Reader();
ir = new InputStreamReader( pomContent );
pom = reader.read( ir );
}
finally
{
IOUtil.close( pomContent );
IOUtil.close( ir );
}
return pom;
}
catch ( Exception e )
{
handleException( request, response, e );
}
return null;
}
protected Object getContent( Variant variant, boolean redirectTo, Request request, Response response )
throws ResourceException
{
Form form = request.getResourceRef().getQueryAsForm();
String groupId = form.getFirstValue( "g" );
String artifactId = form.getFirstValue( "a" );
String version = form.getFirstValue( "v" );
String packaging = form.getFirstValue( "p" );
String classifier = form.getFirstValue( "c" );
String repositoryId = form.getFirstValue( "r" );
String extension = form.getFirstValue( "e" );
if ( groupId == null || artifactId == null || version == null || repositoryId == null )
{
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST );
}
// default the packaging
if ( StringUtils.isBlank( packaging ) )
{
packaging = "jar";
}
ArtifactStoreRequest gavRequest =
getResourceStoreRequest( request, false, false, repositoryId, groupId, artifactId, version, packaging,
classifier, extension );
gavRequest.setRequestLocalOnly( isLocal( request, gavRequest.getRequestPath() ) );
try
{
MavenRepository mavenRepository = getMavenRepository( repositoryId );
ArtifactStoreHelper helper = mavenRepository.getArtifactStoreHelper();
StorageFileItem file = helper.retrieveArtifact( gavRequest );
if ( redirectTo )
{
Reference fileReference =
createRepositoryReference( request, file.getRepositoryItemUid().getRepository().getId(),
file.getRepositoryItemUid().getPath() );
response.setLocationRef( fileReference );
response.setStatus( Status.REDIRECTION_PERMANENT );
String redirectMessage =
"If you are not automatically redirected use this url: " + fileReference.toString();
return redirectMessage;
}
else
{
Representation result = new StorageFileItemRepresentation( file );
result.setDownloadable( true );
result.setDownloadName( file.getName() );
return result;
}
}
catch ( Exception e )
{
handleException( request, response, e );
}
return null;
}
@Override
public Object upload( Context context, Request request, Response response, List<FileItem> files )
throws ResourceException
{
// we have "nibbles": (params,fileA,[fileB])+
// the second file is optional
// if two files are present, one of them should be POM
String repositoryId = null;
boolean hasPom = false;
boolean isPom = false;
InputStream is = null;
String groupId = null;
String artifactId = null;
String version = null;
String classifier = null;
String packaging = null;
String extension = null;
ArtifactCoordinate coords = null;
PomArtifactManager pomManager =
new PomArtifactManager( getNexus().getNexusConfiguration().getTemporaryDirectory() );
try
{
for ( FileItem fi : files )
{
if ( fi.isFormField() )
{
// a parameter
if ( "r".equals( fi.getFieldName() ) )
{
repositoryId = fi.getString();
}
else if ( "g".equals( fi.getFieldName() ) )
{
groupId = fi.getString();
}
else if ( "a".equals( fi.getFieldName() ) )
{
artifactId = fi.getString();
}
else if ( "v".equals( fi.getFieldName() ) )
{
version = fi.getString();
}
else if ( "p".equals( fi.getFieldName() ) )
{
packaging = fi.getString();
}
else if ( "c".equals( fi.getFieldName() ) )
{
classifier = fi.getString();
}
else if ( "e".equals( fi.getFieldName() ) )
{
extension = fi.getString();
}
else if ( "hasPom".equals( fi.getFieldName() ) )
{
hasPom = Boolean.parseBoolean( fi.getString() );
}
coords = new ArtifactCoordinate();
coords.setGroupId( groupId );
coords.setArtifactId( artifactId );
coords.setVersion( version );
coords.setPackaging( packaging );
}
else
{
// a file
isPom = fi.getName().endsWith( ".pom" ) || fi.getName().endsWith( "pom.xml" );
ArtifactStoreRequest gavRequest = null;
if ( hasPom )
{
if ( isPom )
{
// let it "thru" the pomManager to be able to get GAV from it on later pass
pomManager.storeTempPomFile( fi.getInputStream() );
is = pomManager.getTempPomFileInputStream();
}
else
{
is = fi.getInputStream();
}
try
{
coords = pomManager.getArtifactCoordinateFromTempPomFile();
}
catch ( IOException e )
{
getLogger().info( e.getMessage() );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST,
"Error occurred while reading the POM file. Malformed POM?" );
}
if ( isPom )
{
gavRequest =
getResourceStoreRequest( request, true, false, repositoryId, coords.getGroupId(),
coords.getArtifactId(), coords.getVersion(), coords.getPackaging(), null, null );
}
else
{
gavRequest =
getResourceStoreRequest( request, true, false, repositoryId, coords.getGroupId(),
coords.getArtifactId(), coords.getVersion(), coords.getPackaging(), classifier,
extension );
}
}
else
{
is = fi.getInputStream();
gavRequest =
getResourceStoreRequest( request, true, false, repositoryId, groupId, artifactId, version,
packaging, classifier, extension );
}
MavenRepository mr = gavRequest.getMavenRepository();
ArtifactStoreHelper helper = mr.getArtifactStoreHelper();
// temporarily we disable SNAPSHOT upload
// check is it a Snapshot repo
if ( RepositoryPolicy.SNAPSHOT.equals( mr.getRepositoryPolicy() ) )
{
getLogger().info( "Upload to SNAPSHOT maven repository attempted, returning Bad Request." );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST,
"This is a Maven SNAPSHOT repository, and manual upload against it is forbidden!" );
}
if ( !versionMatchesPolicy( gavRequest.getVersion(), mr.getRepositoryPolicy() ) )
{
getLogger().warn( "Version (" + gavRequest.getVersion() + ") and Repository Policy mismatch" );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, "The version "
+ gavRequest.getVersion() + " does not match the repository policy!" );
}
if ( isPom )
{
helper.storeArtifactPom( gavRequest, is, null );
isPom = false;
}
else
{
if ( hasPom )
{
helper.storeArtifact( gavRequest, is, null );
}
else
{
helper.storeArtifactWithGeneratedPom( gavRequest, packaging, is, null );
}
}
}
}
}
catch ( Throwable t )
{
return buildUploadFailedHtmlResponse( t, request, response );
}
finally
{
if ( hasPom )
{
pomManager.removeTempPomFile();
}
}
return coords;
}
protected String buildUploadFailedHtmlResponse( Throwable t, Request request, Response response )
{
try
{
handleException( request, response, t );
}
catch ( ResourceException e )
{
getLogger().debug( "Got error while uploading artifact", t );
StringBuffer resp = new StringBuffer();
resp.append( "<html>" );
resp.append( "<body>" );
resp.append( "<error>" + e.getMessage() + "</error>" );
resp.append( "</body>" );
resp.append( "</html>" );
String forceSuccess = request.getResourceRef().getQueryAsForm().getFirstValue( "forceSuccess" );
if ( !"true".equals( forceSuccess ) )
{
response.setStatus( e.getStatus() );
}
return resp.toString();
}
// We have an error at this point, can't get here
return null;
}
protected void handleException( Request request, Response res, Throwable t )
throws ResourceException
{
if ( t instanceof ResourceException )
{
throw (ResourceException) t;
}
else if ( t instanceof IllegalArgumentException )
{
getLogger().info( "ResourceStoreContentResource, illegal argument:" + t.getMessage() );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, t.getMessage() );
}
else if ( t instanceof RepositoryNotAvailableException )
{
throw new ResourceException( Status.SERVER_ERROR_SERVICE_UNAVAILABLE, t.getMessage() );
}
else if ( t instanceof IllegalRequestException )
{
getLogger().info( "ResourceStoreContentResource, illegal request:" + t.getMessage() );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, t.getMessage() );
}
else if ( t instanceof IllegalOperationException )
{
getLogger().info( "ResourceStoreContentResource, illegal operation:" + t.getMessage() );
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, t.getMessage() );
}
else if ( t instanceof StorageException )
{
getLogger().warn( "IO problem!", t );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL, t.getMessage() );
}
else if ( t instanceof UnsupportedStorageOperationException )
{
throw new ResourceException( Status.CLIENT_ERROR_FORBIDDEN, t.getMessage() );
}
else if ( t instanceof NoSuchResourceStoreException )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND, t.getMessage() );
}
else if ( t instanceof ItemNotFoundException )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND, t.getMessage() );
}
else if ( t instanceof AccessDeniedException )
{
AbstractResourceStoreContentPlexusResource.challengeIfNeeded( request, res, (AccessDeniedException) t );
}
else if ( t instanceof XmlPullParserException )
{
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, t.getMessage() );
}
else if ( t instanceof IOException )
{
getLogger().warn( "IO error!", t );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL, t.getMessage() );
}
else
{
getLogger().warn( t.getMessage(), t );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL, t.getMessage() );
}
}
protected MavenRepository getMavenRepository( String id )
throws ResourceException
{
try
{
Repository repository = getUnprotectedRepositoryRegistry().getRepository( id );
if ( !repository.getRepositoryKind().isFacetAvailable( MavenRepository.class ) )
{
throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, "This is not a Maven repository!" );
}
return repository.adaptToFacet( MavenRepository.class );
}
catch ( NoSuchRepositoryException e )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND, e.getMessage(), e );
}
}
protected boolean versionMatchesPolicy( String version, RepositoryPolicy policy )
{
boolean result = false;
if ( ( RepositoryPolicy.SNAPSHOT.equals( policy ) && VersionUtils.isSnapshot( version ) )
|| ( RepositoryPolicy.RELEASE.equals( policy ) && !VersionUtils.isSnapshot( version ) ) )
{
result = true;
}
return result;
}
}