/**
* 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.proxy.maven;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Map;
import org.apache.maven.index.artifact.ArtifactPackagingMapper;
import org.apache.maven.index.artifact.Gav;
import org.apache.maven.index.artifact.GavCalculator;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.util.StringUtils;
import org.sonatype.nexus.proxy.AccessDeniedException;
import org.sonatype.nexus.proxy.IllegalOperationException;
import org.sonatype.nexus.proxy.ItemNotFoundException;
import org.sonatype.nexus.proxy.LocalStorageException;
import org.sonatype.nexus.proxy.NoSuchResourceStoreException;
import org.sonatype.nexus.proxy.ResourceStoreRequest;
import org.sonatype.nexus.proxy.StorageException;
import org.sonatype.nexus.proxy.attributes.inspectors.DigestCalculatingInspector;
import org.sonatype.nexus.proxy.item.AbstractStorageItem;
import org.sonatype.nexus.proxy.item.DefaultStorageFileItem;
import org.sonatype.nexus.proxy.item.DefaultStorageLinkItem;
import org.sonatype.nexus.proxy.item.RepositoryItemUid;
import org.sonatype.nexus.proxy.item.StorageCollectionItem;
import org.sonatype.nexus.proxy.item.StorageFileItem;
import org.sonatype.nexus.proxy.item.StorageItem;
import org.sonatype.nexus.proxy.item.StorageLinkItem;
import org.sonatype.nexus.proxy.item.StringContentLocator;
import org.sonatype.nexus.proxy.repository.AbstractShadowRepository;
import org.sonatype.nexus.proxy.repository.DefaultRepositoryKind;
import org.sonatype.nexus.proxy.repository.IncompatibleMasterRepositoryException;
import org.sonatype.nexus.proxy.repository.Repository;
import org.sonatype.nexus.proxy.repository.RepositoryKind;
import org.sonatype.nexus.proxy.storage.UnsupportedStorageOperationException;
/**
* Base class for shadows that make "gateways" from M1 to M2 lauouts and vice versa.
*
* @author cstamas
*/
public abstract class LayoutConverterShadowRepository
extends AbstractShadowRepository
implements MavenShadowRepository
{
/**
* The GAV Calculator.
*/
@Requirement( hint = "maven1" )
private GavCalculator m1GavCalculator;
/**
* The GAV Calculator.
*/
@Requirement( hint = "maven2" )
private GavCalculator m2GavCalculator;
/**
* Metadata manager.
*/
@Requirement
private MetadataManager metadataManager;
/**
* The artifact packaging mapper.
*/
@Requirement
private ArtifactPackagingMapper artifactPackagingMapper;
/**
* Repository kind.
*/
private RepositoryKind repositoryKind = new DefaultRepositoryKind( MavenShadowRepository.class,
Arrays.asList( new Class<?>[] { MavenRepository.class } ) );
/**
* ArtifactStoreHelper.
*/
private ArtifactStoreHelper artifactStoreHelper;
public RepositoryKind getRepositoryKind()
{
return repositoryKind;
}
@Override
public MavenRepository getMasterRepository()
{
return super.getMasterRepository().adaptToFacet( MavenRepository.class );
}
@Override
public void setMasterRepository( Repository masterRepository )
throws IncompatibleMasterRepositoryException
{
// we allow only MavenRepository instances as masters
if ( !masterRepository.getRepositoryKind().isFacetAvailable( MavenRepository.class ) )
{
throw new IncompatibleMasterRepositoryException(
"This shadow repository needs master repository which implements MavenRepository interface!", this,
masterRepository.getId() );
}
super.setMasterRepository( masterRepository );
}
public GavCalculator getM1GavCalculator()
{
return m1GavCalculator;
}
public GavCalculator getM2GavCalculator()
{
return m2GavCalculator;
}
public ArtifactPackagingMapper getArtifactPackagingMapper()
{
return artifactPackagingMapper;
}
public RepositoryPolicy getRepositoryPolicy()
{
return getMasterRepository().getRepositoryPolicy();
}
public void setRepositoryPolicy( RepositoryPolicy repositoryPolicy )
{
throw new UnsupportedOperationException( "This method is not supported on Repository of type SHADOW" );
}
public boolean isMavenArtifact( StorageItem item )
{
return isMavenArtifactPath( item.getPath() );
}
public boolean isMavenMetadata( StorageItem item )
{
return isMavenMetadataPath( item.getPath() );
}
public boolean isMavenArtifactPath( String path )
{
return getGavCalculator().pathToGav( path ) != null;
}
public abstract boolean isMavenMetadataPath( String path );
public MetadataManager getMetadataManager()
{
return metadataManager;
}
public boolean recreateMavenMetadata( ResourceStoreRequest request )
{
return false;
}
public void storeItemWithChecksums( ResourceStoreRequest request, InputStream is, Map<String, String> userAttributes )
throws UnsupportedStorageOperationException, IllegalOperationException, StorageException, AccessDeniedException
{
String originalPath = request.getRequestPath();
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "storeItemWithChecksums() :: " + request.getRequestPath() );
}
try
{
try
{
storeItem( request, is, userAttributes );
}
catch ( IOException e )
{
throw new LocalStorageException( "Could not get the content from the ContentLocator!", e );
}
StorageFileItem storedFile = (StorageFileItem) retrieveItem( false, request );
String sha1Hash = storedFile.getAttributes().get( DigestCalculatingInspector.DIGEST_SHA1_KEY );
String md5Hash = storedFile.getAttributes().get( DigestCalculatingInspector.DIGEST_MD5_KEY );
if ( !StringUtils.isEmpty( sha1Hash ) )
{
request.setRequestPath( storedFile.getPath() + ".sha1" );
storeItem( false, new DefaultStorageFileItem( this, request, true, true, new StringContentLocator(
sha1Hash ) ) );
}
if ( !StringUtils.isEmpty( md5Hash ) )
{
request.setRequestPath( storedFile.getPath() + ".md5" );
storeItem( false, new DefaultStorageFileItem( this, request, true, true, new StringContentLocator(
md5Hash ) ) );
}
}
catch ( ItemNotFoundException e )
{
throw new LocalStorageException( "Storage inconsistency!", e );
}
finally
{
request.setRequestPath( originalPath );
}
}
public void deleteItemWithChecksums( ResourceStoreRequest request )
throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException,
StorageException, AccessDeniedException
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "deleteItemWithChecksums() :: " + request.getRequestPath() );
}
try
{
deleteItem( request );
}
catch ( ItemNotFoundException e )
{
if ( request.getRequestPath().endsWith( ".asc" ) )
{
// Do nothing no guarantee that the .asc files will exist
}
else
{
throw e;
}
}
String originalPath = request.getRequestPath();
request.setRequestPath( originalPath + ".sha1" );
try
{
deleteItem( request );
}
catch ( ItemNotFoundException e )
{
// ignore not found
}
request.setRequestPath( originalPath + ".md5" );
try
{
deleteItem( request );
}
catch ( ItemNotFoundException e )
{
// ignore not found
}
// Now remove the .asc files, and the checksums stored with them as well
// Note this is a recursive call, hence the check for .asc
if ( !originalPath.endsWith( ".asc" ) )
{
request.setRequestPath( originalPath + ".asc" );
deleteItemWithChecksums( request );
}
}
public void storeItemWithChecksums( boolean fromTask, AbstractStorageItem item )
throws UnsupportedStorageOperationException, IllegalOperationException, StorageException
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "storeItemWithChecksums() :: " + item.getRepositoryItemUid().toString() );
}
try
{
try
{
storeItem( fromTask, item );
}
catch ( IOException e )
{
throw new LocalStorageException( "Could not get the content from the ContentLocator!", e );
}
StorageFileItem storedFile = (StorageFileItem) retrieveItem( fromTask, new ResourceStoreRequest( item ) );
ResourceStoreRequest req = new ResourceStoreRequest( storedFile );
String sha1Hash = storedFile.getAttributes().get( DigestCalculatingInspector.DIGEST_SHA1_KEY );
String md5Hash = storedFile.getAttributes().get( DigestCalculatingInspector.DIGEST_MD5_KEY );
if ( !StringUtils.isEmpty( sha1Hash ) )
{
req.setRequestPath( item.getPath() + ".sha1" );
storeItem( fromTask, new DefaultStorageFileItem( this, req, true, true, new StringContentLocator(
sha1Hash ) ) );
}
if ( !StringUtils.isEmpty( md5Hash ) )
{
req.setRequestPath( item.getPath() + ".md5" );
storeItem( fromTask, new DefaultStorageFileItem( this, req, true, true, new StringContentLocator(
md5Hash ) ) );
}
}
catch ( ItemNotFoundException e )
{
throw new LocalStorageException( "Storage inconsistency!", e );
}
}
public void deleteItemWithChecksums( boolean fromTask, ResourceStoreRequest request )
throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException, StorageException
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "deleteItemWithChecksums() :: " + request.toString() );
}
deleteItem( fromTask, request );
try
{
request.pushRequestPath( request.getRequestPath() + ".sha1" );
deleteItem( fromTask, request );
}
catch ( ItemNotFoundException e )
{
// ignore not found
}
finally
{
request.popRequestPath();
}
try
{
request.pushRequestPath( request.getRequestPath() + ".md5" );
deleteItem( fromTask, request );
}
catch ( ItemNotFoundException e )
{
// ignore not found
}
finally
{
request.popRequestPath();
}
}
public ArtifactStoreHelper getArtifactStoreHelper()
{
if ( artifactStoreHelper == null )
{
artifactStoreHelper = new ArtifactStoreHelper( this );
}
return artifactStoreHelper;
}
// =================================================================================
// ShadowRepository customizations
/**
* Transforms a full artifact path from M1 layout to M2 layout.
*
* @param path
* @return
*/
protected String transformM1toM2( String path )
{
Gav gav = getM1GavCalculator().pathToGav( path );
// Unsupported path
if ( gav == null )
{
return null;
}
// m2 repo is layouted as:
// g/i/d
// aid
// version
// files
StringBuffer sb = new StringBuffer( RepositoryItemUid.PATH_ROOT );
sb.append( gav.getGroupId().replaceAll( "\\.", "/" ) );
sb.append( RepositoryItemUid.PATH_SEPARATOR );
sb.append( gav.getArtifactId() );
sb.append( RepositoryItemUid.PATH_SEPARATOR );
sb.append( gav.getVersion() );
sb.append( RepositoryItemUid.PATH_SEPARATOR );
sb.append( gav.getName() );
return sb.toString();
}
/**
* Transforms a full artifact path from M2 layout to M1 layout.
*
* @param path
* @return
*/
protected String transformM2toM1( String path )
{
Gav gav = getM2GavCalculator().pathToGav( path );
// Unsupported path
if ( gav == null )
{
return null;
}
// m1 repo is layouted as:
// g.i.d
// poms/jars/java-sources/licenses
// files
StringBuffer sb = new StringBuffer( RepositoryItemUid.PATH_ROOT );
sb.append( gav.getGroupId() );
sb.append( RepositoryItemUid.PATH_SEPARATOR );
sb.append( gav.getExtension() + "s" );
sb.append( RepositoryItemUid.PATH_SEPARATOR );
sb.append( gav.getName() );
return sb.toString();
}
@Override
protected void deleteLink( StorageItem item )
throws UnsupportedStorageOperationException, IllegalOperationException, ItemNotFoundException, StorageException
{
String shadowPath = null;
shadowPath = transformMaster2Shadow( item.getPath() );
if ( shadowPath != null )
{
ResourceStoreRequest request = new ResourceStoreRequest( shadowPath );
request.getRequestContext().putAll( item.getItemContext() );
deleteItem( false, request );
// we need to clean up empty shadow parent directories
String parentPath =
request.getRequestPath().substring( 0, request.getRequestPath().lastIndexOf( item.getName() ) );
ResourceStoreRequest parentRequest = new ResourceStoreRequest( parentPath );
while ( parentRequest != null )
{
StorageItem parentItem = null;
parentItem = this.retrieveItem( false, parentRequest );
// this should be a collection Item
if ( StorageCollectionItem.class.isInstance( parentItem ) )
{
StorageCollectionItem parentCollectionItem = (StorageCollectionItem) parentItem;
try
{
if ( parentCollectionItem.list().size() == 0 )
{
deleteItem( false, parentRequest );
parentRequest = new ResourceStoreRequest( parentCollectionItem.getParentPath() );
}
else
{
// exit loop
parentRequest = null;
}
}
catch ( AccessDeniedException e )
{
this.getLogger().debug(
"Failed to delete shadow parent: " + this.getId() + ":" + parentItem.getPath()
+ " Access Denied", e );
// exit loop
parentRequest = null;
}
catch ( NoSuchResourceStoreException e )
{
this.getLogger().debug(
"Failed to delete shadow parent: " + this.getId() + ":" + parentItem.getPath()
+ " does not exist", e );
// exit loop
parentRequest = null;
}
}
else
{
this.getLogger().debug( "ExpectedCollectionItem, found: " + parentItem.getClass() + ", ignoring." );
}
}
}
}
@Override
protected StorageLinkItem createLink( StorageItem item )
throws UnsupportedStorageOperationException, IllegalOperationException, StorageException
{
String shadowPath = null;
shadowPath = transformMaster2Shadow( item.getPath() );
if ( shadowPath != null )
{
ResourceStoreRequest req = new ResourceStoreRequest( shadowPath );
req.getRequestContext().putAll( item.getItemContext() );
DefaultStorageLinkItem link =
new DefaultStorageLinkItem( this, req, true, true, item.getRepositoryItemUid() );
storeItem( false, link );
return link;
}
else
{
return null;
}
}
/**
* Gets the shadow path from master path. If path is not transformable, return null.
*
* @param path the path
* @return the shadow path
*/
protected abstract String transformMaster2Shadow( String path );
@Override
protected StorageItem doRetrieveItem( ResourceStoreRequest request )
throws IllegalOperationException, ItemNotFoundException, StorageException
{
StorageItem result = null;
try
{
result = super.doRetrieveItem( request );
return result;
}
catch ( ItemNotFoundException e )
{
// if it is thrown by super.doRetrieveItem()
String transformedPath = null;
transformedPath = transformShadow2Master( request.getRequestPath() );
if ( transformedPath == null )
{
throw new ItemNotFoundException( request, this );
}
// delegate the call to the master
request.pushRequestPath( transformedPath );
try
{
result = doRetrieveItemFromMaster( request );
}
finally
{
request.popRequestPath();
}
// try to create link on the fly
try
{
StorageLinkItem link = createLink( result );
if ( link != null )
{
return link;
}
else
{
// fallback to result, but will not happen, see above
return result;
}
}
catch ( Exception e1 )
{
// fallback to result, but will not happen, see above
return result;
}
}
}
/**
* Gets the master path from shadow path. If path is not transformable, return null.
*
* @param path the path
* @return the master path
*/
protected abstract String transformShadow2Master( String path );
}