001package org.apache.archiva.stagerepository.merge; 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.common.filelock.DefaultFileLockManager; 023import org.apache.archiva.common.utils.VersionComparator; 024import org.apache.archiva.common.utils.VersionUtil; 025import org.apache.archiva.configuration.ArchivaConfiguration; 026import org.apache.archiva.configuration.Configuration; 027import org.apache.archiva.configuration.ManagedRepositoryConfiguration; 028import org.apache.archiva.maven2.metadata.MavenMetadataReader; 029import org.apache.archiva.metadata.model.ArtifactMetadata; 030import org.apache.archiva.metadata.repository.MetadataRepository; 031import org.apache.archiva.metadata.repository.MetadataRepositoryException; 032import org.apache.archiva.filter.Filter; 033import org.apache.archiva.metadata.repository.RepositorySession; 034import org.apache.archiva.metadata.repository.RepositorySessionFactory; 035import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator; 036import org.apache.archiva.model.ArchivaRepositoryMetadata; 037import org.apache.archiva.repository.RepositoryException; 038import org.apache.archiva.repository.RepositoryType; 039import org.apache.archiva.repository.metadata.RepositoryMetadataException; 040import org.apache.archiva.repository.metadata.base.RepositoryMetadataWriter; 041import org.apache.archiva.repository.storage.FilesystemAsset; 042import org.apache.archiva.repository.storage.FilesystemStorage; 043import org.apache.archiva.repository.storage.StorageAsset; 044import org.apache.archiva.xml.XMLException; 045import org.apache.commons.io.FileUtils; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048import org.springframework.stereotype.Service; 049 050import javax.inject.Inject; 051import javax.inject.Named; 052import java.io.BufferedWriter; 053import java.io.IOException; 054import java.nio.file.Files; 055import java.nio.file.Path; 056import java.nio.file.Paths; 057import java.text.DateFormat; 058import java.text.SimpleDateFormat; 059import java.util.*; 060 061/** 062 * 063 */ 064@Service ("repositoryMerger#maven2") 065public class Maven2RepositoryMerger 066 implements RepositoryMerger 067{ 068 069 private Logger log = LoggerFactory.getLogger( getClass() ); 070 071 private static final Comparator<ArtifactMetadata> META_COMPARATOR = Comparator.comparing(ArtifactMetadata::getNamespace) 072 .thenComparing(ArtifactMetadata::getProject) 073 .thenComparing(ArtifactMetadata::getId) 074 .thenComparing(ArtifactMetadata::getVersion); 075 076 /** 077 * 078 */ 079 private ArchivaConfiguration configuration; 080 081 /** 082 * 083 */ 084 private RepositoryPathTranslator pathTranslator; 085 086 private static final String METADATA_FILENAME = "maven-metadata.xml"; 087 088 @Inject 089 private RepositorySessionFactory repositorySessionFactory; 090 091 @Inject 092 public Maven2RepositoryMerger( 093 @Named (value = "archivaConfiguration#default") ArchivaConfiguration archivaConfiguration, 094 @Named (value = "repositoryPathTranslator#maven2") RepositoryPathTranslator repositoryPathTranslator ) 095 { 096 this.configuration = archivaConfiguration; 097 this.pathTranslator = repositoryPathTranslator; 098 } 099 100 public void setConfiguration( ArchivaConfiguration configuration ) 101 { 102 this.configuration = configuration; 103 } 104 105 @Override 106 public boolean supportsRepository( RepositoryType type ) 107 { 108 return RepositoryType.MAVEN.equals( type ); 109 } 110 111 @Override 112 public void merge( MetadataRepository metadataRepository, String sourceRepoId, String targetRepoId ) 113 throws RepositoryMergerException 114 { 115 116 try(RepositorySession session = repositorySessionFactory.createSession()) 117 { 118 List<ArtifactMetadata> artifactsInSourceRepo = metadataRepository.getArtifacts(session , sourceRepoId ); 119 for ( ArtifactMetadata artifactMetadata : artifactsInSourceRepo ) 120 { 121 artifactMetadata.setRepositoryId( targetRepoId ); 122 createFolderStructure( sourceRepoId, targetRepoId, artifactMetadata ); 123 } 124 } 125 catch ( MetadataRepositoryException e ) 126 { 127 throw new RepositoryMergerException( e.getMessage(), e ); 128 } 129 catch ( IOException e ) 130 { 131 throw new RepositoryMergerException( e.getMessage(), e ); 132 } 133 catch ( RepositoryException e ) 134 { 135 throw new RepositoryMergerException( e.getMessage(), e ); 136 } 137 } 138 139 // TODO when UI needs a subset to merge 140 @Override 141 public void merge( MetadataRepository metadataRepository, String sourceRepoId, String targetRepoId, 142 Filter<ArtifactMetadata> filter ) 143 throws RepositoryMergerException 144 { 145 try(RepositorySession session = repositorySessionFactory.createSession()) 146 { 147 List<ArtifactMetadata> sourceArtifacts = metadataRepository.getArtifacts(session , sourceRepoId ); 148 for ( ArtifactMetadata metadata : sourceArtifacts ) 149 { 150 if ( filter.accept( metadata ) ) 151 { 152 createFolderStructure( sourceRepoId, targetRepoId, metadata ); 153 } 154 } 155 } 156 catch ( MetadataRepositoryException e ) 157 { 158 throw new RepositoryMergerException( e.getMessage(), e ); 159 } 160 catch ( IOException e ) 161 { 162 throw new RepositoryMergerException( e.getMessage(), e ); 163 } 164 catch ( RepositoryException e ) 165 { 166 throw new RepositoryMergerException( e.getMessage(), e ); 167 } 168 } 169 170 private void createFolderStructure( String sourceRepoId, String targetRepoId, ArtifactMetadata artifactMetadata ) 171 throws IOException, RepositoryException 172 { 173 Configuration config = configuration.getConfiguration(); 174 175 ManagedRepositoryConfiguration targetRepoConfig = config.findManagedRepositoryById( targetRepoId ); 176 177 ManagedRepositoryConfiguration sourceRepoConfig = config.findManagedRepositoryById( sourceRepoId ); 178 179 Date lastUpdatedTimestamp = Calendar.getInstance().getTime(); 180 181 TimeZone timezone = TimeZone.getTimeZone( "UTC" ); 182 183 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" ); 184 185 fmt.setTimeZone( timezone ); 186 187 String timestamp = fmt.format( lastUpdatedTimestamp ); 188 189 String targetRepoPath = targetRepoConfig.getLocation(); 190 191 String sourceRepoPath = sourceRepoConfig.getLocation(); 192 193 String artifactPath = pathTranslator.toPath( artifactMetadata.getNamespace(), artifactMetadata.getProject(), 194 artifactMetadata.getProjectVersion(), artifactMetadata.getId() ); 195 196 Path sourceArtifactFile = Paths.get( sourceRepoPath, artifactPath ); 197 198 Path targetArtifactFile = Paths.get( targetRepoPath, artifactPath ); 199 200 log.debug( "artifactPath {}", artifactPath ); 201 202 int lastIndex = artifactPath.lastIndexOf( RepositoryPathTranslator.PATH_SEPARATOR ); 203 204 Path targetFile = Paths.get( targetRepoPath, artifactPath.substring( 0, lastIndex ) ); 205 206 if ( !Files.exists(targetFile) ) 207 { 208 // create the folder structure when it does not exist 209 Files.createDirectories(targetFile); 210 } 211 // artifact copying 212 copyFile( sourceArtifactFile, targetArtifactFile ); 213 214 // pom file copying 215 // TODO need to use path translator to get the pom file path 216// String fileName = artifactMetadata.getProject() + "-" + artifactMetadata.getVersion() + ".pom"; 217// 218// File sourcePomFile = 219// pathTranslator.toFile( new File( sourceRepoPath ), artifactMetadata.getId(), artifactMetadata.getProject(), 220// artifactMetadata.getVersion(), fileName ); 221// 222// String relativePathToPomFile = sourcePomFile.getAbsolutePath().split( sourceRepoPath )[1]; 223// File targetPomFile = new File( targetRepoPath, relativePathToPomFile ); 224 225 //pom file copying (file path is taken with out using path translator) 226 227 String index = artifactPath.substring( lastIndex + 1 ); 228 int last = index.lastIndexOf( '.' ); 229 Path sourcePomFile = Paths.get( sourceRepoPath, 230 artifactPath.substring( 0, lastIndex ) + "/" + artifactPath.substring( 231 lastIndex + 1 ).substring( 0, last ) + ".pom" ); 232 Path targetPomFile = Paths.get( targetRepoPath, 233 artifactPath.substring( 0, lastIndex ) + "/" + artifactPath.substring( 234 lastIndex + 1 ).substring( 0, last ) + ".pom" ); 235 236 if ( !Files.exists(targetPomFile) && Files.exists(sourcePomFile) ) 237 { 238 copyFile( sourcePomFile, targetPomFile ); 239 } 240 241 // explicitly update only if metadata-updater consumer is not enabled! 242 if ( !config.getRepositoryScanning().getKnownContentConsumers().contains( "metadata-updater" ) ) 243 { 244 245 // updating version metadata files 246 FilesystemStorage fsStorage = new FilesystemStorage(Paths.get(sourceRepoPath), new DefaultFileLockManager()); 247 248 StorageAsset versionMetaDataFileInSourceRepo = 249 pathTranslator.toFile( new FilesystemAsset(fsStorage, "", Paths.get(sourceRepoPath)), artifactMetadata.getNamespace(), 250 artifactMetadata.getProject(), artifactMetadata.getVersion(), 251 METADATA_FILENAME ); 252 253 if ( versionMetaDataFileInSourceRepo.exists() ) 254 {//Pattern quote for windows path 255 String relativePathToVersionMetadataFile = 256 getRelativeAssetPath(versionMetaDataFileInSourceRepo); 257 Path versionMetaDataFileInTargetRepo = Paths.get( targetRepoPath, relativePathToVersionMetadataFile ); 258 259 if ( !Files.exists(versionMetaDataFileInTargetRepo) ) 260 { 261 copyFile( versionMetaDataFileInSourceRepo.getFilePath(), versionMetaDataFileInTargetRepo ); 262 } 263 else 264 { 265 updateVersionMetadata( versionMetaDataFileInTargetRepo, artifactMetadata, lastUpdatedTimestamp ); 266 267 } 268 } 269 270 // updating project meta data file 271 StorageAsset projectDirectoryInSourceRepo = versionMetaDataFileInSourceRepo.getParent().getParent(); 272 StorageAsset projectMetadataFileInSourceRepo = projectDirectoryInSourceRepo.resolve(METADATA_FILENAME ); 273 274 if ( projectMetadataFileInSourceRepo.exists() ) 275 { 276 String relativePathToProjectMetadataFile = 277 getRelativeAssetPath(projectMetadataFileInSourceRepo); 278 Path projectMetadataFileInTargetRepo = Paths.get( targetRepoPath, relativePathToProjectMetadataFile ); 279 280 if ( !Files.exists(projectMetadataFileInTargetRepo) ) 281 { 282 283 copyFile( projectMetadataFileInSourceRepo.getFilePath(), projectMetadataFileInTargetRepo ); 284 } 285 else 286 { 287 updateProjectMetadata( projectMetadataFileInTargetRepo, artifactMetadata, lastUpdatedTimestamp, 288 timestamp ); 289 } 290 } 291 } 292 293 } 294 295 private String getRelativeAssetPath(final StorageAsset asset) { 296 String relPath = asset.getPath(); 297 while(relPath.startsWith("/")) { 298 relPath = relPath.substring(1); 299 } 300 return relPath; 301 } 302 303 private void copyFile( Path sourceFile, Path targetFile ) 304 throws IOException 305 { 306 307 FileUtils.copyFile( sourceFile.toFile(), targetFile.toFile() ); 308 309 } 310 311 private void updateProjectMetadata( Path projectMetaDataFileIntargetRepo, ArtifactMetadata artifactMetadata, 312 Date lastUpdatedTimestamp, String timestamp ) 313 throws RepositoryMetadataException 314 { 315 ArrayList<String> availableVersions = new ArrayList<>(); 316 String latestVersion = artifactMetadata.getProjectVersion(); 317 318 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetaDataFileIntargetRepo ); 319 320 if ( Files.exists(projectMetaDataFileIntargetRepo) ) 321 { 322 availableVersions = (ArrayList<String>) projectMetadata.getAvailableVersions(); 323 324 Collections.sort( availableVersions, VersionComparator.getInstance() ); 325 326 if ( !availableVersions.contains( artifactMetadata.getVersion() ) ) 327 { 328 availableVersions.add( artifactMetadata.getVersion() ); 329 } 330 331 latestVersion = availableVersions.get( availableVersions.size() - 1 ); 332 } 333 else 334 { 335 availableVersions.add( artifactMetadata.getProjectVersion() ); 336 projectMetadata.setGroupId( artifactMetadata.getNamespace() ); 337 projectMetadata.setArtifactId( artifactMetadata.getProject() ); 338 } 339 340 if ( projectMetadata.getGroupId() == null ) 341 { 342 projectMetadata.setGroupId( artifactMetadata.getNamespace() ); 343 } 344 345 if ( projectMetadata.getArtifactId() == null ) 346 { 347 projectMetadata.setArtifactId( artifactMetadata.getProject() ); 348 } 349 350 projectMetadata.setLatestVersion( latestVersion ); 351 projectMetadata.setAvailableVersions( availableVersions ); 352 projectMetadata.setLastUpdated( timestamp ); 353 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp ); 354 355 if ( !VersionUtil.isSnapshot( artifactMetadata.getVersion() ) ) 356 { 357 projectMetadata.setReleasedVersion( latestVersion ); 358 } 359 360 try(BufferedWriter writer = Files.newBufferedWriter(projectMetaDataFileIntargetRepo)) { 361 RepositoryMetadataWriter.write( projectMetadata, writer ); 362 } catch (IOException e) { 363 throw new RepositoryMetadataException(e); 364 } 365 366 } 367 368 private void updateVersionMetadata( Path versionMetaDataFileInTargetRepo, ArtifactMetadata artifactMetadata, 369 Date lastUpdatedTimestamp ) 370 throws RepositoryMetadataException 371 { 372 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetaDataFileInTargetRepo ); 373 if ( !Files.exists(versionMetaDataFileInTargetRepo) ) 374 { 375 versionMetadata.setGroupId( artifactMetadata.getNamespace() ); 376 versionMetadata.setArtifactId( artifactMetadata.getProject() ); 377 versionMetadata.setVersion( artifactMetadata.getProjectVersion() ); 378 } 379 380 versionMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp ); 381 try(BufferedWriter writer = Files.newBufferedWriter(versionMetaDataFileInTargetRepo) ) { 382 RepositoryMetadataWriter.write( versionMetadata, writer); 383 } catch (IOException e) { 384 throw new RepositoryMetadataException(e); 385 } 386 } 387 388 private ArchivaRepositoryMetadata getMetadata( Path metadataFile ) 389 throws RepositoryMetadataException 390 { 391 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata(); 392 if ( Files.exists(metadataFile) ) 393 { 394 try 395 { 396 metadata = MavenMetadataReader.read( metadataFile ); 397 } 398 catch (XMLException e ) 399 { 400 throw new RepositoryMetadataException( e.getMessage(), e ); 401 } 402 } 403 return metadata; 404 } 405 406 @Override 407 public List<ArtifactMetadata> getConflictingArtifacts( MetadataRepository metadataRepository, String sourceRepo, 408 String targetRepo ) 409 throws RepositoryMergerException 410 { 411 try(RepositorySession session = repositorySessionFactory.createSession()) 412 { 413 TreeSet<ArtifactMetadata> targetArtifacts = new TreeSet<>(META_COMPARATOR); 414 targetArtifacts.addAll(metadataRepository.getArtifacts(session , targetRepo )); 415 TreeSet<ArtifactMetadata> sourceArtifacts = new TreeSet<>(META_COMPARATOR); 416 sourceArtifacts.addAll(metadataRepository.getArtifacts(session , sourceRepo )); 417 sourceArtifacts.retainAll(targetArtifacts); 418 419 return new ArrayList<>(sourceArtifacts); 420 } 421 catch ( MetadataRepositoryException e ) 422 { 423 throw new RepositoryMergerException( e.getMessage(), e ); 424 } 425 } 426 427 public RepositorySessionFactory getRepositorySessionFactory( ) 428 { 429 return repositorySessionFactory; 430 } 431 432 public void setRepositorySessionFactory( RepositorySessionFactory repositorySessionFactory ) 433 { 434 this.repositorySessionFactory = repositorySessionFactory; 435 } 436}