001package org.apache.archiva.metadata.repository; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.metadata.model.ArtifactMetadata; 023import org.apache.archiva.metadata.model.ProjectMetadata; 024import org.apache.archiva.metadata.model.ProjectVersionMetadata; 025import org.apache.archiva.metadata.model.ProjectVersionReference; 026import org.apache.archiva.metadata.repository.filter.ExcludesFilter; 027import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest; 028import org.apache.archiva.metadata.repository.storage.RepositoryStorage; 029import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException; 030import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException; 031import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException; 032import org.apache.archiva.redback.components.cache.Cache; 033import org.apache.archiva.repository.events.RepositoryListener; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036import org.springframework.beans.factory.annotation.Autowired; 037import org.springframework.stereotype.Service; 038 039import javax.inject.Inject; 040import javax.inject.Named; 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.List; 044 045/** 046 * <p> 047 * Default implementation of the metadata resolver API. At present it will handle updating the content repository 048 * from new or changed information in the model and artifacts from the repository storage. 049 * </p> 050 * <p> 051 * This is a singleton component to allow an alternate implementation to be provided. It is intended to be the same 052 * system-wide for the whole content repository instead of on a per-managed-repository basis. Therefore, the session is 053 * passed in as an argument to obtain any necessary resources, rather than the class being instantiated within the 054 * session in the context of a single managed repository's resolution needs. 055 * </p> 056 * <p> 057 * Note that the caller is responsible for the session, such as closing and saving (which is implied by the resolver 058 * being obtained from within the session). The {@link RepositorySession#markDirty()} method is used as a hint to ensure 059 * that the session knows we've made changes at close. We cannot ensure the changes will be persisted if the caller 060 * chooses to revert first. This is preferable to storing the metadata immediately - a separate session would require 061 * having a bi-directional link with the session factory, and saving the existing session might save other changes 062 * unknowingly by the caller. 063 * </p> 064 */ 065@Service("metadataResolver#default") 066public class DefaultMetadataResolver 067 implements MetadataResolver 068{ 069 070 private Logger log = LoggerFactory.getLogger( DefaultMetadataResolver.class ); 071 072 /** 073 * <p> 074 * FIXME: this needs to be configurable based on storage type - and could also be instantiated per repo. Change to a 075 * factory, and perhaps retrieve from the session. We should avoid creating one per request, however. 076 * </p> 077 * <p> 078 * TODO: Also need to accommodate availability of proxy module 079 * ... could be a different type since we need methods to modify the storage metadata, which would also allow more 080 * appropriate methods to pass in the already determined repository configuration, for example, instead of the ID 081 * </p> 082 */ 083 @Inject 084 @Named(value = "repositoryStorage#maven2") 085 private RepositoryStorage repositoryStorage; 086 087 @Inject 088 @Autowired(required = false) 089 private List<RepositoryListener> listeners = new ArrayList<>(); 090 091 /** 092 * Cache used for namespaces 093 */ 094 @Inject 095 @Named( value = "cache#namespaces" ) 096 private Cache<String, Collection<String>> namespacesCache; 097 098 @Override 099 public ProjectVersionMetadata resolveProjectVersion( RepositorySession session, String repoId, String namespace, 100 String projectId, String projectVersion ) 101 throws MetadataResolutionException 102 { 103 MetadataRepository metadataRepository = session.getRepository(); 104 105 ProjectVersionMetadata metadata = 106 metadataRepository.getProjectVersion( repoId, namespace, projectId, projectVersion ); 107 // TODO: do we want to detect changes as well by comparing timestamps? isProjectVersionNewerThan(updated) 108 // in such cases we might also remove/update stale metadata, including adjusting plugin-based facets 109 // This would also be better than checking for completeness - we can then refresh only when fixed (though 110 // sometimes this has an additional dependency - such as a parent - requesting the user to force an update 111 // may then work here and be more efficient than always trying again) 112 if ( metadata == null || metadata.isIncomplete() ) 113 { 114 try 115 { 116 ReadMetadataRequest readMetadataRequest = 117 new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId( 118 projectId ).projectVersion( projectVersion ).browsingRequest( true ); 119 metadata = repositoryStorage.readProjectVersionMetadata( readMetadataRequest ); 120 121 log.debug( "Resolved project version metadata from storage: {}", metadata ); 122 123 // FIXME: make this a more generic post-processing that plugins can take advantage of 124 // eg. maven projects should be able to process parent here 125 if ( !metadata.getDependencies().isEmpty() ) 126 { 127 ProjectVersionReference ref = new ProjectVersionReference(); 128 ref.setNamespace( namespace ); 129 ref.setProjectId( projectId ); 130 ref.setProjectVersion( projectVersion ); 131 ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY ); 132 } 133 try 134 { 135 for ( RepositoryListener listener : listeners ) 136 { 137 listener.addArtifact( session, repoId, namespace, projectId, metadata ); 138 } 139 metadataRepository.updateProjectVersion( repoId, namespace, projectId, metadata ); 140 } 141 catch ( MetadataRepositoryException e ) 142 { 143 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 144 } 145 146 session.markDirty(); 147 } 148 catch ( RepositoryStorageMetadataInvalidException e ) 149 { 150 for ( RepositoryListener listener : listeners ) 151 { 152 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 153 } 154 throw new MetadataResolutionException( e.getMessage(), e ); 155 } 156 catch ( RepositoryStorageMetadataNotFoundException e ) 157 { 158 for ( RepositoryListener listener : listeners ) 159 { 160 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 161 } 162 // no need to rethrow - return null 163 } 164 catch ( RepositoryStorageRuntimeException e ) 165 { 166 for ( RepositoryListener listener : listeners ) 167 { 168 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 169 } 170 throw new MetadataResolutionException( e.getMessage(), e ); 171 } 172 173 } 174 return metadata; 175 } 176 177 @Override 178 public Collection<ProjectVersionReference> resolveProjectReferences( RepositorySession session, String repoId, 179 String namespace, String projectId, 180 String projectVersion ) 181 throws MetadataResolutionException 182 { 183 // TODO: is this assumption correct? could a storage mech. actually know all references in a non-Maven scenario? 184 // not passed to the storage mechanism as resolving references would require iterating all artifacts 185 MetadataRepository metadataRepository = session.getRepository(); 186 return metadataRepository.getProjectReferences( repoId, namespace, projectId, projectVersion ); 187 } 188 189 @Override 190 public Collection<String> resolveRootNamespaces( RepositorySession session, String repoId ) 191 throws MetadataResolutionException 192 { 193 try 194 { 195 196 Collection<String> namespaces = namespacesCache.get( repoId ); 197 if ( namespaces != null ) 198 { 199 return namespaces; 200 } 201 202 MetadataRepository metadataRepository = session.getRepository(); 203 namespaces = metadataRepository.getRootNamespaces( repoId ); 204 Collection<String> storageNamespaces = 205 repositoryStorage.listRootNamespaces( repoId, new ExcludesFilter<String>( namespaces ) ); 206 if ( storageNamespaces != null && !storageNamespaces.isEmpty() ) 207 { 208 209 log.debug( "Resolved root namespaces from storage: {}", storageNamespaces ); 210 211 for ( String n : storageNamespaces ) 212 { 213 try 214 { 215 metadataRepository.updateNamespace( repoId, n ); 216 // just invalidate cache entry 217 String cacheKey = repoId + "-" + n; 218 namespacesCache.remove( cacheKey ); 219 } 220 catch ( MetadataRepositoryException e ) 221 { 222 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 223 } 224 } 225 session.markDirty(); 226 227 namespaces = new ArrayList<>( namespaces ); 228 namespaces.addAll( storageNamespaces ); 229 } 230 231 namespacesCache.put( repoId, namespaces ); 232 233 return namespaces; 234 } 235 catch ( RepositoryStorageRuntimeException e ) 236 { 237 throw new MetadataResolutionException( e.getMessage(), e ); 238 } 239 } 240 241 @Override 242 public Collection<String> resolveNamespaces( RepositorySession session, String repoId, String namespace ) 243 throws MetadataResolutionException 244 { 245 try 246 { 247 MetadataRepository metadataRepository = session.getRepository(); 248 String cacheKey = repoId + "-" + namespace; 249 Collection<String> namespaces = namespacesCache.get( cacheKey ); 250 if ( namespaces == null ) 251 { 252 namespaces = metadataRepository.getNamespaces( repoId, namespace ); 253 namespacesCache.put( cacheKey, namespaces ); 254 } 255 Collection<String> exclusions = new ArrayList<>( namespaces ); 256 exclusions.addAll( metadataRepository.getProjects( repoId, namespace ) ); 257 Collection<String> storageNamespaces = 258 repositoryStorage.listNamespaces( repoId, namespace, new ExcludesFilter<String>( exclusions ) ); 259 if ( storageNamespaces != null && !storageNamespaces.isEmpty() ) 260 { 261 262 log.debug( "Resolved namespaces from storage: {}", storageNamespaces ); 263 264 for ( String n : storageNamespaces ) 265 { 266 try 267 { 268 metadataRepository.updateNamespace( repoId, namespace + "." + n ); 269 // just invalidate cache entry 270 cacheKey = repoId + "-" + namespace + "." + n; 271 namespacesCache.remove( cacheKey ); 272 } 273 catch ( MetadataRepositoryException e ) 274 { 275 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 276 } 277 } 278 session.markDirty(); 279 280 namespaces = new ArrayList<>( namespaces ); 281 namespaces.addAll( storageNamespaces ); 282 } 283 return namespaces; 284 } 285 catch ( RepositoryStorageRuntimeException e ) 286 { 287 throw new MetadataResolutionException( e.getMessage(), e ); 288 } 289 } 290 291 @Override 292 public Collection<String> resolveProjects( RepositorySession session, String repoId, String namespace ) 293 throws MetadataResolutionException 294 { 295 try 296 { 297 MetadataRepository metadataRepository = session.getRepository(); 298 Collection<String> projects = metadataRepository.getProjects( repoId, namespace ); 299 Collection<String> exclusions = new ArrayList<>( projects ); 300 301 String cacheKey = repoId + "-" + namespace; 302 Collection<String> namespaces = namespacesCache.get( cacheKey ); 303 if ( namespaces == null ) 304 { 305 namespaces = metadataRepository.getNamespaces( repoId, namespace ); 306 namespacesCache.put( cacheKey, namespaces ); 307 } 308 309 exclusions.addAll( namespaces ); 310 311 Collection<String> storageProjects = 312 repositoryStorage.listProjects( repoId, namespace, new ExcludesFilter<>( exclusions ) ); 313 if ( storageProjects != null && !storageProjects.isEmpty() ) 314 { 315 316 log.debug( "Resolved projects from storage: {}", storageProjects ); 317 for ( String projectId : storageProjects ) 318 { 319 ProjectMetadata projectMetadata = 320 repositoryStorage.readProjectMetadata( repoId, namespace, projectId ); 321 if ( projectMetadata != null ) 322 { 323 try 324 { 325 metadataRepository.updateProject( repoId, projectMetadata ); 326 } 327 catch ( MetadataRepositoryException e ) 328 { 329 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 330 } 331 } 332 } 333 session.markDirty(); 334 335 projects = new ArrayList<>( projects ); 336 projects.addAll( storageProjects ); 337 } 338 return projects; 339 } 340 catch ( RepositoryStorageRuntimeException e ) 341 { 342 throw new MetadataResolutionException( e.getMessage(), e ); 343 } 344 } 345 346 @Override 347 public Collection<String> resolveProjectVersions( RepositorySession session, String repoId, String namespace, 348 String projectId ) 349 throws MetadataResolutionException 350 { 351 try 352 { 353 MetadataRepository metadataRepository = session.getRepository(); 354 355 Collection<String> projectVersions = metadataRepository.getProjectVersions( repoId, namespace, projectId ); 356 Collection<String> storageProjectVersions = 357 repositoryStorage.listProjectVersions( repoId, namespace, projectId, 358 new ExcludesFilter<String>( projectVersions ) ); 359 if ( storageProjectVersions != null && !storageProjectVersions.isEmpty() ) 360 { 361 log.debug( "Resolved project versions from storage: {}", storageProjectVersions ); 362 363 for ( String projectVersion : storageProjectVersions ) 364 { 365 try 366 { 367 ReadMetadataRequest readMetadataRequest = 368 new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId( 369 projectId ).projectVersion( projectVersion ); 370 ProjectVersionMetadata versionMetadata = 371 repositoryStorage.readProjectVersionMetadata( readMetadataRequest ); 372 for ( RepositoryListener listener : listeners ) 373 { 374 listener.addArtifact( session, repoId, namespace, projectId, versionMetadata ); 375 } 376 377 metadataRepository.updateProjectVersion( repoId, namespace, projectId, versionMetadata ); 378 } 379 catch ( MetadataRepositoryException e ) 380 { 381 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 382 } 383 catch ( RepositoryStorageMetadataInvalidException e ) 384 { 385 log.warn( 386 "Not update project in metadata repository due to an error resolving it from storage: {}", 387 e.getMessage() ); 388 389 for ( RepositoryListener listener : listeners ) 390 { 391 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 392 } 393 } 394 catch ( RepositoryStorageMetadataNotFoundException e ) 395 { 396 for ( RepositoryListener listener : listeners ) 397 { 398 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 399 } 400 } 401 } 402 session.markDirty(); 403 404 projectVersions = new ArrayList<>( projectVersions ); 405 projectVersions.addAll( storageProjectVersions ); 406 } 407 return projectVersions; 408 } 409 catch ( RepositoryStorageRuntimeException e ) 410 { 411 throw new MetadataResolutionException( e.getMessage(), e ); 412 } 413 } 414 415 @Override 416 public Collection<ArtifactMetadata> resolveArtifacts( RepositorySession session, String repoId, String namespace, 417 String projectId, String projectVersion ) 418 throws MetadataResolutionException 419 { 420 try 421 { 422 MetadataRepository metadataRepository = session.getRepository(); 423 Collection<ArtifactMetadata> artifacts = 424 metadataRepository.getArtifacts( repoId, namespace, projectId, projectVersion ); 425 ExcludesFilter<String> filter = new ExcludesFilter<String>( createArtifactIdList( artifacts ) ); 426 427 ReadMetadataRequest readMetadataRequest = 428 new ReadMetadataRequest().repositoryId( repoId ).namespace( namespace ).projectId( 429 projectId ).projectVersion( projectVersion ).filter( filter ); 430 431 Collection<ArtifactMetadata> storageArtifacts = 432 repositoryStorage.readArtifactsMetadata( readMetadataRequest ); 433 if ( storageArtifacts != null && !storageArtifacts.isEmpty() ) 434 { 435 436 log.debug( "Resolved artifacts from storage: {}", storageArtifacts ); 437 438 for ( ArtifactMetadata artifact : storageArtifacts ) 439 { 440 try 441 { 442 metadataRepository.updateArtifact( repoId, namespace, projectId, projectVersion, artifact ); 443 } 444 catch ( MetadataRepositoryException e ) 445 { 446 log.warn( "Unable to persist resolved information: {}", e.getMessage(), e ); 447 } 448 } 449 session.markDirty(); 450 451 artifacts = new ArrayList<>( artifacts ); 452 artifacts.addAll( storageArtifacts ); 453 } 454 return artifacts; 455 } 456 catch ( RepositoryStorageRuntimeException e ) 457 { 458 for ( RepositoryListener listener : listeners ) 459 { 460 listener.addArtifactProblem( session, repoId, namespace, projectId, projectVersion, e ); 461 } 462 throw new MetadataResolutionException( e.getMessage(), e ); 463 } 464 } 465 466 private Collection<String> createArtifactIdList( Collection<ArtifactMetadata> artifacts ) 467 { 468 Collection<String> artifactIds = new ArrayList<>(); 469 for ( ArtifactMetadata artifact : artifacts ) 470 { 471 artifactIds.add( artifact.getId() ); 472 } 473 return artifactIds; 474 } 475}