blob: d12a29fc848e051721fa7b970f7fad32334eee06 [file] [log] [blame]
package org.apache.archiva.metadata.repository;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import org.apache.archiva.metadata.model.ArtifactMetadata;
import org.apache.archiva.metadata.model.ProjectMetadata;
import org.apache.archiva.metadata.model.ProjectVersionMetadata;
import org.apache.archiva.metadata.model.ProjectVersionReference;
import org.apache.archiva.metadata.repository.filter.ExcludesFilter;
import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest;
import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException;
import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException;
import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException;
import org.apache.archiva.redback.components.cache.Cache;
import org.apache.archiva.repository.events.RepositoryListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* Default implementation of the metadata resolver API. At present it will handle updating the content repository
* from new or changed information in the model and artifacts from the repository storage.
* </p>
* <p>
* This is a singleton component to allow an alternate implementation to be provided. It is intended to be the same
* system-wide for the whole content repository instead of on a per-managed-repository basis. Therefore, the session is
* passed in as an argument to obtain any necessary resources, rather than the class being instantiated within the
* session in the context of a single managed repository's resolution needs.
* </p>
* <p>
* Note that the caller is responsible for the session, such as closing and saving (which is implied by the resolver
* being obtained from within the session). The {@link RepositorySession#markDirty()} method is used as a hint to ensure
* that the session knows we've made changes at close. We cannot ensure the changes will be persisted if the caller
* chooses to revert first. This is preferable to storing the metadata immediately - a separate session would require
* having a bi-directional link with the session factory, and saving the existing session might save other changes
* unknowingly by the caller.
* </p>
*/
@Service("metadataResolver#default")
public class DefaultMetadataResolver
implements MetadataResolver
{
private Logger log = LoggerFactory.getLogger( DefaultMetadataResolver.class );
/**
* <p>
* FIXME: this needs to be configurable based on storage type - and could also be instantiated per repo. Change to a
* factory, and perhaps retrieve from the session. We should avoid creating one per request, however.
* </p>
* <p>
* TODO: Also need to accommodate availability of proxy module
* ... could be a different type since we need methods to modify the storage metadata, which would also allow more
* appropriate methods to pass in the already determined repository configuration, for example, instead of the ID
* </p>
*/
@Inject
@Named(value = "repositoryStorage#maven2")
private RepositoryStorage repositoryStorage;
@Inject
@Autowired(required = false)
private List<RepositoryListener> listeners = new ArrayList<>();
/**
* Cache used for namespaces
*/
@Inject
@Named( value = "cache#namespaces" )
private Cache<String, Collection<String>> namespacesCache;
@Override
public ProjectVersionMetadata resolveProjectVersion( RepositorySession session, String repoId, String namespace,
String projectId, String projectVersion )
throws MetadataResolutionException
{
MetadataRepository metadataRepository = session.getRepository();
ProjectVersionMetadata metadata =
metadataRepository.getProjectVersion( repoId, namespace, projectId, projectVersion );
// TODO: do we want to detect changes as well by comparing timestamps? isProjectVersionNewerThan(updated)
// in such cases we might also remove/update stale metadata, including adjusting plugin-based facets
// This would also be better than checking for completeness - we can then refresh only when fixed (though
// sometimes this has an additional dependency - such as a parent - requesting the user to force an update
// may then work here and be more efficient than always trying again)
if ( metadata == null || metadata.isIncomplete() )
{
try
{
ReadMetadataRequest readMetadataRequest =
new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId(
projectId ).projectVersion( projectVersion ).browsingRequest( true );
metadata = repositoryStorage.readProjectVersionMetadata( readMetadataRequest );
log.debug( "Resolved project version metadata from storage: {}", metadata );
// FIXME: make this a more generic post-processing that plugins can take advantage of
// eg. maven projects should be able to process parent here
if ( !metadata.getDependencies().isEmpty() )
{
ProjectVersionReference ref = new ProjectVersionReference();
ref.setNamespace( namespace );
ref.setProjectId( projectId );
ref.setProjectVersion( projectVersion );
ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY );
}
try
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifact( session, repoId, namespace, projectId, metadata );
}
metadataRepository.updateProjectVersion( repoId, namespace, projectId, metadata );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
session.markDirty();
}
catch ( RepositoryStorageMetadataInvalidException e )
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
throw new MetadataResolutionException( e.getMessage(), e );
}
catch ( RepositoryStorageMetadataNotFoundException e )
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
// no need to rethrow - return null
}
catch ( RepositoryStorageRuntimeException e )
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
throw new MetadataResolutionException( e.getMessage(), e );
}
}
return metadata;
}
@Override
public Collection<ProjectVersionReference> resolveProjectReferences( RepositorySession session, String repoId,
String namespace, String projectId,
String projectVersion )
throws MetadataResolutionException
{
// TODO: is this assumption correct? could a storage mech. actually know all references in a non-Maven scenario?
// not passed to the storage mechanism as resolving references would require iterating all artifacts
MetadataRepository metadataRepository = session.getRepository();
return metadataRepository.getProjectReferences( repoId, namespace, projectId, projectVersion );
}
@Override
public Collection<String> resolveRootNamespaces( RepositorySession session, String repoId )
throws MetadataResolutionException
{
try
{
Collection<String> namespaces = namespacesCache.get( repoId );
if ( namespaces != null )
{
return namespaces;
}
MetadataRepository metadataRepository = session.getRepository();
namespaces = metadataRepository.getRootNamespaces( repoId );
Collection<String> storageNamespaces =
repositoryStorage.listRootNamespaces( repoId, new ExcludesFilter<String>( namespaces ) );
if ( storageNamespaces != null && !storageNamespaces.isEmpty() )
{
log.debug( "Resolved root namespaces from storage: {}", storageNamespaces );
for ( String n : storageNamespaces )
{
try
{
metadataRepository.updateNamespace( repoId, n );
// just invalidate cache entry
String cacheKey = repoId + "-" + n;
namespacesCache.remove( cacheKey );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
}
session.markDirty();
namespaces = new ArrayList<>( namespaces );
namespaces.addAll( storageNamespaces );
}
namespacesCache.put( repoId, namespaces );
return namespaces;
}
catch ( RepositoryStorageRuntimeException e )
{
throw new MetadataResolutionException( e.getMessage(), e );
}
}
@Override
public Collection<String> resolveNamespaces( RepositorySession session, String repoId, String namespace )
throws MetadataResolutionException
{
try
{
MetadataRepository metadataRepository = session.getRepository();
String cacheKey = repoId + "-" + namespace;
Collection<String> namespaces = namespacesCache.get( cacheKey );
if ( namespaces == null )
{
namespaces = metadataRepository.getNamespaces( repoId, namespace );
namespacesCache.put( cacheKey, namespaces );
}
Collection<String> exclusions = new ArrayList<>( namespaces );
exclusions.addAll( metadataRepository.getProjects( repoId, namespace ) );
Collection<String> storageNamespaces =
repositoryStorage.listNamespaces( repoId, namespace, new ExcludesFilter<String>( exclusions ) );
if ( storageNamespaces != null && !storageNamespaces.isEmpty() )
{
log.debug( "Resolved namespaces from storage: {}", storageNamespaces );
for ( String n : storageNamespaces )
{
try
{
metadataRepository.updateNamespace( repoId, namespace + "." + n );
// just invalidate cache entry
cacheKey = repoId + "-" + namespace + "." + n;
namespacesCache.remove( cacheKey );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
}
session.markDirty();
namespaces = new ArrayList<>( namespaces );
namespaces.addAll( storageNamespaces );
}
return namespaces;
}
catch ( RepositoryStorageRuntimeException e )
{
throw new MetadataResolutionException( e.getMessage(), e );
}
}
@Override
public Collection<String> resolveProjects( RepositorySession session, String repoId, String namespace )
throws MetadataResolutionException
{
try
{
MetadataRepository metadataRepository = session.getRepository();
Collection<String> projects = metadataRepository.getProjects( repoId, namespace );
Collection<String> exclusions = new ArrayList<>( projects );
String cacheKey = repoId + "-" + namespace;
Collection<String> namespaces = namespacesCache.get( cacheKey );
if ( namespaces == null )
{
namespaces = metadataRepository.getNamespaces( repoId, namespace );
namespacesCache.put( cacheKey, namespaces );
}
exclusions.addAll( namespaces );
Collection<String> storageProjects =
repositoryStorage.listProjects( repoId, namespace, new ExcludesFilter<>( exclusions ) );
if ( storageProjects != null && !storageProjects.isEmpty() )
{
log.debug( "Resolved projects from storage: {}", storageProjects );
for ( String projectId : storageProjects )
{
ProjectMetadata projectMetadata =
repositoryStorage.readProjectMetadata( repoId, namespace, projectId );
if ( projectMetadata != null )
{
try
{
metadataRepository.updateProject( repoId, projectMetadata );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
}
}
session.markDirty();
projects = new ArrayList<>( projects );
projects.addAll( storageProjects );
}
return projects;
}
catch ( RepositoryStorageRuntimeException e )
{
throw new MetadataResolutionException( e.getMessage(), e );
}
}
@Override
public Collection<String> resolveProjectVersions( RepositorySession session, String repoId, String namespace,
String projectId )
throws MetadataResolutionException
{
try
{
MetadataRepository metadataRepository = session.getRepository();
Collection<String> projectVersions = metadataRepository.getProjectVersions( repoId, namespace, projectId );
Collection<String> storageProjectVersions =
repositoryStorage.listProjectVersions( repoId, namespace, projectId,
new ExcludesFilter<String>( projectVersions ) );
if ( storageProjectVersions != null && !storageProjectVersions.isEmpty() )
{
log.debug( "Resolved project versions from storage: {}", storageProjectVersions );
for ( String projectVersion : storageProjectVersions )
{
try
{
ReadMetadataRequest readMetadataRequest =
new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId(
projectId ).projectVersion( projectVersion );
ProjectVersionMetadata versionMetadata =
repositoryStorage.readProjectVersionMetadata( readMetadataRequest );
for ( RepositoryListener listener : listeners )
{
listener.addArtifact( session, repoId, namespace, projectId, versionMetadata );
}
metadataRepository.updateProjectVersion( repoId, namespace, projectId, versionMetadata );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
catch ( RepositoryStorageMetadataInvalidException e )
{
log.warn(
"Not update project in metadata repository due to an error resolving it from storage: {}",
e.getMessage() );
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
}
catch ( RepositoryStorageMetadataNotFoundException e )
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
}
}
session.markDirty();
projectVersions = new ArrayList<>( projectVersions );
projectVersions.addAll( storageProjectVersions );
}
return projectVersions;
}
catch ( RepositoryStorageRuntimeException e )
{
throw new MetadataResolutionException( e.getMessage(), e );
}
}
@Override
public Collection<ArtifactMetadata> resolveArtifacts( RepositorySession session, String repoId, String namespace,
String projectId, String projectVersion )
throws MetadataResolutionException
{
try
{
MetadataRepository metadataRepository = session.getRepository();
Collection<ArtifactMetadata> artifacts =
metadataRepository.getArtifacts( repoId, namespace, projectId, projectVersion );
ExcludesFilter<String> filter = new ExcludesFilter<String>( createArtifactIdList( artifacts ) );
ReadMetadataRequest readMetadataRequest =
new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId(
projectId ).projectVersion( projectVersion ).filter( filter );
Collection<ArtifactMetadata> storageArtifacts =
repositoryStorage.readArtifactsMetadata( readMetadataRequest );
if ( storageArtifacts != null && !storageArtifacts.isEmpty() )
{
log.debug( "Resolved artifacts from storage: {}", storageArtifacts );
for ( ArtifactMetadata artifact : storageArtifacts )
{
try
{
metadataRepository.updateArtifact( repoId, namespace, projectId, projectVersion, artifact );
}
catch ( MetadataRepositoryException e )
{
log.warn( "Unable to persist resolved information: {}", e.getMessage(), e );
}
}
session.markDirty();
artifacts = new ArrayList<>( artifacts );
artifacts.addAll( storageArtifacts );
}
return artifacts;
}
catch ( RepositoryStorageRuntimeException e )
{
for ( RepositoryListener listener : listeners )
{
listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e );
}
throw new MetadataResolutionException( e.getMessage(), e );
}
}
private Collection<String> createArtifactIdList( Collection<ArtifactMetadata> artifacts )
{
Collection<String> artifactIds = new ArrayList<>();
for ( ArtifactMetadata artifact : artifacts )
{
artifactIds.add( artifact.getId() );
}
return artifactIds;
}
}