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; } }