001 package org.apache.archiva.metadata.repository.storage.maven2; 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 022 import org.apache.archiva.admin.model.RepositoryAdminException; 023 import org.apache.archiva.admin.model.beans.ManagedRepository; 024 import org.apache.archiva.admin.model.beans.NetworkProxy; 025 import org.apache.archiva.admin.model.beans.ProxyConnector; 026 import org.apache.archiva.admin.model.beans.RemoteRepository; 027 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; 028 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin; 029 import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin; 030 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin; 031 import org.apache.archiva.checksum.ChecksumAlgorithm; 032 import org.apache.archiva.checksum.ChecksummedFile; 033 import org.apache.archiva.common.utils.VersionUtil; 034 import org.apache.archiva.maven2.metadata.MavenMetadataReader; 035 import org.apache.archiva.metadata.model.ArtifactMetadata; 036 import org.apache.archiva.metadata.model.ProjectMetadata; 037 import org.apache.archiva.metadata.model.ProjectVersionMetadata; 038 import org.apache.archiva.metadata.repository.filter.Filter; 039 import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest; 040 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator; 041 import org.apache.archiva.metadata.repository.storage.RepositoryStorage; 042 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException; 043 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException; 044 import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException; 045 import org.apache.archiva.model.ArchivaRepositoryMetadata; 046 import org.apache.archiva.model.ArtifactReference; 047 import org.apache.archiva.model.SnapshotVersion; 048 import org.apache.archiva.policies.ProxyDownloadException; 049 import org.apache.archiva.proxy.common.WagonFactory; 050 import org.apache.archiva.proxy.model.RepositoryProxyConnectors; 051 import org.apache.archiva.reports.RepositoryProblemFacet; 052 import org.apache.archiva.repository.ManagedRepositoryContent; 053 import org.apache.archiva.xml.XMLException; 054 import org.apache.commons.lang.StringUtils; 055 import org.apache.maven.model.CiManagement; 056 import org.apache.maven.model.Dependency; 057 import org.apache.maven.model.DistributionManagement; 058 import org.apache.maven.model.IssueManagement; 059 import org.apache.maven.model.License; 060 import org.apache.maven.model.MailingList; 061 import org.apache.maven.model.Model; 062 import org.apache.maven.model.Organization; 063 import org.apache.maven.model.Relocation; 064 import org.apache.maven.model.Scm; 065 import org.apache.maven.model.building.DefaultModelBuilderFactory; 066 import org.apache.maven.model.building.DefaultModelBuildingRequest; 067 import org.apache.maven.model.building.ModelBuilder; 068 import org.apache.maven.model.building.ModelBuildingException; 069 import org.apache.maven.model.building.ModelBuildingRequest; 070 import org.apache.maven.model.building.ModelProblem; 071 import org.apache.maven.model.io.xpp3.MavenXpp3Reader; 072 import org.codehaus.plexus.util.xml.pull.XmlPullParserException; 073 import org.slf4j.Logger; 074 import org.slf4j.LoggerFactory; 075 import org.springframework.context.ApplicationContext; 076 import org.springframework.stereotype.Service; 077 078 import javax.annotation.PostConstruct; 079 import javax.inject.Inject; 080 import javax.inject.Named; 081 import java.io.File; 082 import java.io.FileNotFoundException; 083 import java.io.FileReader; 084 import java.io.FilenameFilter; 085 import java.io.IOException; 086 import java.util.ArrayList; 087 import java.util.Arrays; 088 import java.util.Collection; 089 import java.util.Collections; 090 import java.util.Date; 091 import java.util.HashMap; 092 import java.util.List; 093 import java.util.Map; 094 095 /** 096 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to 097 * deal with rather than being instantiated per-repository. 098 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session). 099 * <p/> 100 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated 101 * within the session in the context of a single managed repository's resolution needs. 102 * <p/> 103 */ 104 @Service ( "repositoryStorage#maven2" ) 105 public class Maven2RepositoryStorage 106 implements RepositoryStorage 107 { 108 /** 109 * 110 */ 111 private ModelBuilder builder; 112 113 /** 114 * 115 */ 116 @Inject 117 private RemoteRepositoryAdmin remoteRepositoryAdmin; 118 119 @Inject 120 private ManagedRepositoryAdmin managedRepositoryAdmin; 121 122 @Inject 123 private ProxyConnectorAdmin proxyConnectorAdmin; 124 125 @Inject 126 private NetworkProxyAdmin networkProxyAdmin; 127 128 /** 129 * 130 */ 131 @Inject 132 @Named ( value = "repositoryPathTranslator#maven2" ) 133 private RepositoryPathTranslator pathTranslator; 134 135 @Inject 136 private WagonFactory wagonFactory; 137 138 @Inject 139 private ApplicationContext applicationContext; 140 141 private static final Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class ); 142 143 private static final String METADATA_FILENAME_START = "maven-metadata"; 144 145 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml"; 146 147 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader(); 148 149 150 @PostConstruct 151 public void initialize() 152 { 153 DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory(); 154 builder = defaultModelBuilderFactory.newInstance(); 155 156 } 157 158 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId ) 159 { 160 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there? 161 return null; 162 } 163 164 public ProjectVersionMetadata readProjectVersionMetadata( ReadMetadataRequest readMetadataRequest ) 165 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException, 166 RepositoryStorageRuntimeException 167 { 168 try 169 { 170 ManagedRepository managedRepository = 171 managedRepositoryAdmin.getManagedRepository( readMetadataRequest.getRepositoryId() ); 172 173 String artifactVersion = readMetadataRequest.getProjectVersion(); 174 if ( VersionUtil.isSnapshot( 175 readMetadataRequest.getProjectVersion() ) ) // skygo trying to improve speed by honoring managed configuration MRM-1658 176 { 177 if ( managedRepository.isReleases() && !managedRepository.isSnapshots() ) 178 { 179 throw new RepositoryStorageRuntimeException( "lookforsnaponreleaseonly", 180 "managed repo is configured for release only" ); 181 } 182 } 183 else 184 { 185 if ( !managedRepository.isReleases() && managedRepository.isSnapshots() ) 186 { 187 throw new RepositoryStorageRuntimeException( "lookforsreleaseonsneponly", 188 "managed repo is configured for snapshot only" ); 189 } 190 } 191 File basedir = new File( managedRepository.getLocation() ); 192 if ( VersionUtil.isSnapshot( readMetadataRequest.getProjectVersion() ) ) 193 { 194 File metadataFile = pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), 195 readMetadataRequest.getProjectId(), 196 readMetadataRequest.getProjectVersion(), METADATA_FILENAME ); 197 try 198 { 199 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile ); 200 201 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename 202 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion(); 203 if ( snapshotVersion != null ) 204 { 205 artifactVersion = 206 artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end 207 artifactVersion = 208 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber(); 209 } 210 } 211 catch ( XMLException e ) 212 { 213 // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version 214 log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() ); 215 } 216 } 217 218 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator 219 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom"; 220 File file = 221 pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(), 222 readMetadataRequest.getProjectVersion(), id ); 223 224 if ( !file.exists() ) 225 { 226 // metadata could not be resolved 227 throw new RepositoryStorageMetadataNotFoundException( 228 "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" ); 229 } 230 231 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache 232 // anything locally! 233 List<RemoteRepository> remoteRepositories = new ArrayList<RemoteRepository>(); 234 Map<String, NetworkProxy> networkProxies = new HashMap<String, NetworkProxy>(); 235 236 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyConnectorAdmin.getProxyConnectorAsMap(); 237 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get( readMetadataRequest.getRepositoryId() ); 238 if ( proxyConnectors != null ) 239 { 240 for ( ProxyConnector proxyConnector : proxyConnectors ) 241 { 242 RemoteRepository remoteRepoConfig = 243 remoteRepositoryAdmin.getRemoteRepository( proxyConnector.getTargetRepoId() ); 244 245 if ( remoteRepoConfig != null ) 246 { 247 remoteRepositories.add( remoteRepoConfig ); 248 249 NetworkProxy networkProxyConfig = 250 networkProxyAdmin.getNetworkProxy( proxyConnector.getProxyId() ); 251 252 if ( networkProxyConfig != null ) 253 { 254 // key/value: remote repo ID/proxy info 255 networkProxies.put( proxyConnector.getTargetRepoId(), networkProxyConfig ); 256 } 257 } 258 } 259 } 260 261 ModelBuildingRequest req = 262 new DefaultModelBuildingRequest().setProcessPlugins( false ).setPomFile( file ).setTwoPhaseBuilding( 263 false ).setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL ); 264 265 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm 266 req.setSystemProperties( System.getProperties() ); 267 268 // MRM-1411 269 req.setModelResolver( 270 new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories, 271 networkProxies, managedRepository ) ); 272 273 Model model; 274 try 275 { 276 model = builder.build( req ).getEffectiveModel(); 277 } 278 catch ( ModelBuildingException e ) 279 { 280 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage(); 281 282 List<ModelProblem> modelProblems = e.getProblems(); 283 for ( ModelProblem problem : modelProblems ) 284 { 285 // MRM-1411, related to MRM-1335 286 // this means that the problem was that the parent wasn't resolved! 287 // olamy really hackhish but fail with java profile so use error message 288 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) ) 289 // but setTwoPhaseBuilding(true) fix that 290 if ( ( problem.getException() instanceof FileNotFoundException && e.getModelId() != null && 291 !e.getModelId().equals( problem.getModelId() ) ) ) 292 { 293 log.warn( "The artifact's parent POM file '" + file + "' cannot be resolved. " + 294 "Using defaults for project version metadata.." ); 295 296 ProjectVersionMetadata metadata = new ProjectVersionMetadata(); 297 metadata.setId( readMetadataRequest.getProjectVersion() ); 298 299 MavenProjectFacet facet = new MavenProjectFacet(); 300 facet.setGroupId( readMetadataRequest.getNamespace() ); 301 facet.setArtifactId( readMetadataRequest.getProjectId() ); 302 facet.setPackaging( "jar" ); 303 metadata.addFacet( facet ); 304 305 String errMsg = 306 "Error in resolving artifact's parent POM file. " + ( problem.getException() == null 307 ? problem.getMessage() 308 : problem.getException().getMessage() ); 309 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet(); 310 repoProblemFacet.setRepositoryId( readMetadataRequest.getRepositoryId() ); 311 repoProblemFacet.setId( readMetadataRequest.getRepositoryId() ); 312 repoProblemFacet.setMessage( errMsg ); 313 repoProblemFacet.setProblem( errMsg ); 314 repoProblemFacet.setProject( readMetadataRequest.getProjectId() ); 315 repoProblemFacet.setVersion( readMetadataRequest.getProjectVersion() ); 316 repoProblemFacet.setNamespace( readMetadataRequest.getNamespace() ); 317 318 metadata.addFacet( repoProblemFacet ); 319 320 return metadata; 321 } 322 } 323 324 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e ); 325 } 326 327 // Check if the POM is in the correct location 328 boolean correctGroupId = readMetadataRequest.getNamespace().equals( model.getGroupId() ); 329 boolean correctArtifactId = readMetadataRequest.getProjectId().equals( model.getArtifactId() ); 330 boolean correctVersion = readMetadataRequest.getProjectVersion().equals( model.getVersion() ); 331 if ( !correctGroupId || !correctArtifactId || !correctVersion ) 332 { 333 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" ); 334 if ( !correctGroupId ) 335 { 336 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() ); 337 } 338 if ( !correctArtifactId ) 339 { 340 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() ); 341 } 342 if ( !correctVersion ) 343 { 344 message.append( "\nIncorrect version: " ).append( model.getVersion() ); 345 } 346 347 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() ); 348 } 349 350 ProjectVersionMetadata metadata = new ProjectVersionMetadata(); 351 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) ); 352 metadata.setDescription( model.getDescription() ); 353 metadata.setId( readMetadataRequest.getProjectVersion() ); 354 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) ); 355 metadata.setLicenses( convertLicenses( model.getLicenses() ) ); 356 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) ); 357 metadata.setDependencies( convertDependencies( model.getDependencies() ) ); 358 metadata.setName( model.getName() ); 359 metadata.setOrganization( convertOrganization( model.getOrganization() ) ); 360 metadata.setScm( convertScm( model.getScm() ) ); 361 metadata.setUrl( model.getUrl() ); 362 363 MavenProjectFacet facet = new MavenProjectFacet(); 364 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() ); 365 facet.setArtifactId( model.getArtifactId() ); 366 facet.setPackaging( model.getPackaging() ); 367 if ( model.getParent() != null ) 368 { 369 MavenProjectParent parent = new MavenProjectParent(); 370 parent.setGroupId( model.getParent().getGroupId() ); 371 parent.setArtifactId( model.getParent().getArtifactId() ); 372 parent.setVersion( model.getParent().getVersion() ); 373 facet.setParent( parent ); 374 } 375 metadata.addFacet( facet ); 376 377 return metadata; 378 } 379 catch ( RepositoryAdminException e ) 380 { 381 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e); 382 } 383 } 384 385 public void setWagonFactory( WagonFactory wagonFactory ) 386 { 387 this.wagonFactory = wagonFactory; 388 } 389 390 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies ) 391 { 392 List<org.apache.archiva.metadata.model.Dependency> l = 393 new ArrayList<org.apache.archiva.metadata.model.Dependency>(); 394 for ( Dependency dependency : dependencies ) 395 { 396 org.apache.archiva.metadata.model.Dependency newDependency = 397 new org.apache.archiva.metadata.model.Dependency(); 398 newDependency.setArtifactId( dependency.getArtifactId() ); 399 newDependency.setClassifier( dependency.getClassifier() ); 400 newDependency.setGroupId( dependency.getGroupId() ); 401 newDependency.setOptional( dependency.isOptional() ); 402 newDependency.setScope( dependency.getScope() ); 403 newDependency.setSystemPath( dependency.getSystemPath() ); 404 newDependency.setType( dependency.getType() ); 405 newDependency.setVersion( dependency.getVersion() ); 406 l.add( newDependency ); 407 } 408 return l; 409 } 410 411 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm ) 412 { 413 org.apache.archiva.metadata.model.Scm newScm = null; 414 if ( scm != null ) 415 { 416 newScm = new org.apache.archiva.metadata.model.Scm(); 417 newScm.setConnection( scm.getConnection() ); 418 newScm.setDeveloperConnection( scm.getDeveloperConnection() ); 419 newScm.setUrl( scm.getUrl() ); 420 } 421 return newScm; 422 } 423 424 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization ) 425 { 426 org.apache.archiva.metadata.model.Organization org = null; 427 if ( organization != null ) 428 { 429 org = new org.apache.archiva.metadata.model.Organization(); 430 org.setName( organization.getName() ); 431 org.setUrl( organization.getUrl() ); 432 } 433 return org; 434 } 435 436 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses ) 437 { 438 List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>(); 439 for ( License license : licenses ) 440 { 441 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License(); 442 newLicense.setName( license.getName() ); 443 newLicense.setUrl( license.getUrl() ); 444 l.add( newLicense ); 445 } 446 return l; 447 } 448 449 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists ) 450 { 451 List<org.apache.archiva.metadata.model.MailingList> l = 452 new ArrayList<org.apache.archiva.metadata.model.MailingList>(); 453 for ( MailingList mailingList : mailingLists ) 454 { 455 org.apache.archiva.metadata.model.MailingList newMailingList = 456 new org.apache.archiva.metadata.model.MailingList(); 457 newMailingList.setName( mailingList.getName() ); 458 newMailingList.setMainArchiveUrl( mailingList.getArchive() ); 459 newMailingList.setPostAddress( mailingList.getPost() ); 460 newMailingList.setSubscribeAddress( mailingList.getSubscribe() ); 461 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() ); 462 newMailingList.setOtherArchives( mailingList.getOtherArchives() ); 463 l.add( newMailingList ); 464 } 465 return l; 466 } 467 468 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement ) 469 { 470 org.apache.archiva.metadata.model.IssueManagement im = null; 471 if ( issueManagement != null ) 472 { 473 im = new org.apache.archiva.metadata.model.IssueManagement(); 474 im.setSystem( issueManagement.getSystem() ); 475 im.setUrl( issueManagement.getUrl() ); 476 } 477 return im; 478 } 479 480 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement ) 481 { 482 org.apache.archiva.metadata.model.CiManagement ci = null; 483 if ( ciManagement != null ) 484 { 485 ci = new org.apache.archiva.metadata.model.CiManagement(); 486 ci.setSystem( ciManagement.getSystem() ); 487 ci.setUrl( ciManagement.getUrl() ); 488 } 489 return ci; 490 } 491 492 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter ) 493 throws RepositoryStorageRuntimeException 494 { 495 File dir = getRepositoryBasedir( repoId ); 496 497 return getSortedFiles( dir, filter ); 498 } 499 500 private static Collection<String> getSortedFiles( File dir, Filter<String> filter ) 501 { 502 List<String> fileNames; 503 String[] files = dir.list( new DirectoryFilter( filter ) ); 504 if ( files != null ) 505 { 506 fileNames = new ArrayList<String>( Arrays.asList( files ) ); 507 Collections.sort( fileNames ); 508 } 509 else 510 { 511 fileNames = Collections.emptyList(); 512 } 513 return fileNames; 514 } 515 516 private File getRepositoryBasedir( String repoId ) 517 throws RepositoryStorageRuntimeException 518 { 519 try 520 { 521 ManagedRepository repositoryConfiguration = managedRepositoryAdmin.getManagedRepository( repoId ); 522 523 return new File( repositoryConfiguration.getLocation() ); 524 } 525 catch ( RepositoryAdminException e ) 526 { 527 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e); 528 } 529 } 530 531 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter ) 532 throws RepositoryStorageRuntimeException 533 { 534 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace ); 535 536 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded 537 List<String> namespaces = new ArrayList<String>(); 538 File[] files = dir.listFiles( new DirectoryFilter( filter ) ); 539 if ( files != null ) 540 { 541 for ( File file : files ) 542 { 543 if ( !isProject( file, filter ) ) 544 { 545 namespaces.add( file.getName() ); 546 } 547 } 548 } 549 Collections.sort( namespaces ); 550 return namespaces; 551 } 552 553 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter ) 554 throws RepositoryStorageRuntimeException 555 { 556 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace ); 557 558 // scan all directories in the namespace, and only include those that are known to be projects 559 List<String> projects = new ArrayList<String>(); 560 File[] files = dir.listFiles( new DirectoryFilter( filter ) ); 561 if ( files != null ) 562 { 563 for ( File file : files ) 564 { 565 if ( isProject( file, filter ) ) 566 { 567 projects.add( file.getName() ); 568 } 569 } 570 } 571 Collections.sort( projects ); 572 return projects; 573 } 574 575 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId, 576 Filter<String> filter ) 577 throws RepositoryStorageRuntimeException 578 { 579 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId ); 580 581 // all directories in a project directory can be considered a version 582 return getSortedFiles( dir, filter ); 583 } 584 585 public Collection<ArtifactMetadata> readArtifactsMetadata( ReadMetadataRequest readMetadataRequest ) 586 throws RepositoryStorageRuntimeException 587 { 588 File dir = pathTranslator.toFile( getRepositoryBasedir( readMetadataRequest.getRepositoryId() ), 589 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(), 590 readMetadataRequest.getProjectVersion() ); 591 592 // all files that are not metadata and not a checksum / signature are considered artifacts 593 File[] files = dir.listFiles( new ArtifactDirectoryFilter( readMetadataRequest.getFilter() ) ); 594 595 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>(); 596 if ( files != null ) 597 { 598 for ( File file : files ) 599 { 600 ArtifactMetadata metadata = 601 getArtifactFromFile( readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(), 602 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(), 603 file ); 604 artifacts.add( metadata ); 605 } 606 } 607 return artifacts; 608 } 609 610 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path ) 611 throws RepositoryStorageRuntimeException 612 { 613 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path ); 614 615 populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) ); 616 617 return metadata; 618 } 619 620 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId, 621 String projectVersion, File file ) 622 { 623 ArtifactMetadata metadata = 624 pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() ); 625 626 populateArtifactMetadataFromFile( metadata, file ); 627 628 return metadata; 629 } 630 631 /** 632 * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do 633 * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the 634 * metadatas. 635 * <p/> 636 * For such clients, archiva does server-side relocation by reading itself the <relocation> element in 637 * metadatas and serving the expected artifact. 638 */ 639 public void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact ) 640 throws ProxyDownloadException 641 { 642 if ( "pom".equals( artifact.getType() ) ) 643 { 644 return; 645 } 646 647 // Build the artifact POM reference 648 ArtifactReference pomReference = new ArtifactReference(); 649 pomReference.setGroupId( artifact.getGroupId() ); 650 pomReference.setArtifactId( artifact.getArtifactId() ); 651 pomReference.setVersion( artifact.getVersion() ); 652 pomReference.setType( "pom" ); 653 654 RepositoryProxyConnectors connectors = 655 applicationContext.getBean( "repositoryProxyConnectors#default", RepositoryProxyConnectors.class ); 656 657 // Get the artifact POM from proxied repositories if needed 658 connectors.fetchFromProxies( managedRepository, pomReference ); 659 660 // Open and read the POM from the managed repo 661 File pom = managedRepository.toFile( pomReference ); 662 663 if ( !pom.exists() ) 664 { 665 return; 666 } 667 668 try 669 { 670 // MavenXpp3Reader leaves the file open, so we need to close it ourselves. 671 FileReader reader = new FileReader( pom ); 672 Model model = null; 673 try 674 { 675 model = MAVEN_XPP_3_READER.read( reader ); 676 } 677 finally 678 { 679 if ( reader != null ) 680 { 681 reader.close(); 682 } 683 } 684 685 DistributionManagement dist = model.getDistributionManagement(); 686 if ( dist != null ) 687 { 688 Relocation relocation = dist.getRelocation(); 689 if ( relocation != null ) 690 { 691 // artifact is relocated : update the repositoryPath 692 if ( relocation.getGroupId() != null ) 693 { 694 artifact.setGroupId( relocation.getGroupId() ); 695 } 696 if ( relocation.getArtifactId() != null ) 697 { 698 artifact.setArtifactId( relocation.getArtifactId() ); 699 } 700 if ( relocation.getVersion() != null ) 701 { 702 artifact.setVersion( relocation.getVersion() ); 703 } 704 } 705 } 706 } 707 catch ( FileNotFoundException e ) 708 { 709 // Artifact has no POM in repo : ignore 710 } 711 catch ( IOException e ) 712 { 713 // Unable to read POM : ignore. 714 } 715 catch ( XmlPullParserException e ) 716 { 717 // Invalid POM : ignore 718 } 719 } 720 721 //----------------------------- 722 // internal 723 //----------------------------- 724 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file ) 725 { 726 metadata.setWhenGathered( new Date() ); 727 metadata.setFileLastModified( file.lastModified() ); 728 ChecksummedFile checksummedFile = new ChecksummedFile( file ); 729 try 730 { 731 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) ); 732 } 733 catch ( IOException e ) 734 { 735 log.error( "Unable to checksum file {}: {},MD5", file, e.getMessage() ); 736 } 737 try 738 { 739 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) ); 740 } 741 catch ( IOException e ) 742 { 743 log.error( "Unable to checksum file {}: {},SHA1", file, e.getMessage() ); 744 } 745 metadata.setSize( file.length() ); 746 } 747 748 private boolean isProject( File dir, Filter<String> filter ) 749 { 750 // scan directories for a valid project version subdirectory, meaning this must be a project directory 751 File[] files = dir.listFiles( new DirectoryFilter( filter ) ); 752 if ( files != null ) 753 { 754 for ( File file : files ) 755 { 756 if ( isProjectVersion( file ) ) 757 { 758 return true; 759 } 760 } 761 } 762 763 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project 764 ArchivaRepositoryMetadata metadata = readMetadata( dir ); 765 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) ) 766 { 767 return true; 768 } 769 770 return false; 771 } 772 773 private boolean isProjectVersion( File dir ) 774 { 775 final String artifactId = dir.getParentFile().getName(); 776 final String projectVersion = dir.getName(); 777 778 // check if there is a POM artifact file to ensure it is a version directory 779 File[] files; 780 if ( VersionUtil.isSnapshot( projectVersion ) ) 781 { 782 files = dir.listFiles( new PomFilenameFilter( artifactId, projectVersion ) ); 783 } 784 else 785 { 786 final String pomFile = artifactId + "-" + projectVersion + ".pom"; 787 files = dir.listFiles( new PomFileFilter( pomFile ) ); 788 } 789 if ( files != null && files.length > 0 ) 790 { 791 return true; 792 } 793 794 // if a metadata file is present, check if this is the "version" directory, marking it as a project version 795 ArchivaRepositoryMetadata metadata = readMetadata( dir ); 796 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) ) 797 { 798 return true; 799 } 800 801 return false; 802 } 803 804 private ArchivaRepositoryMetadata readMetadata( File directory ) 805 { 806 ArchivaRepositoryMetadata metadata = null; 807 File metadataFile = new File( directory, METADATA_FILENAME ); 808 if ( metadataFile.exists() ) 809 { 810 try 811 { 812 metadata = MavenMetadataReader.read( metadataFile ); 813 } 814 catch ( XMLException e ) 815 { 816 // ignore missing or invalid metadata 817 } 818 } 819 return metadata; 820 } 821 822 private static class DirectoryFilter 823 implements FilenameFilter 824 { 825 private final Filter<String> filter; 826 827 public DirectoryFilter( Filter<String> filter ) 828 { 829 this.filter = filter; 830 } 831 832 public boolean accept( File dir, String name ) 833 { 834 if ( !filter.accept( name ) ) 835 { 836 return false; 837 } 838 else if ( name.startsWith( "." ) ) 839 { 840 return false; 841 } 842 else if ( !new File( dir, name ).isDirectory() ) 843 { 844 return false; 845 } 846 return true; 847 } 848 } 849 850 private static class ArtifactDirectoryFilter 851 implements FilenameFilter 852 { 853 private final Filter<String> filter; 854 855 private ArtifactDirectoryFilter( Filter<String> filter ) 856 { 857 this.filter = filter; 858 } 859 860 public boolean accept( File dir, String name ) 861 { 862 // TODO compare to logic in maven-repository-layer 863 if ( !filter.accept( name ) ) 864 { 865 return false; 866 } 867 else if ( name.startsWith( "." ) ) 868 { 869 return false; 870 } 871 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) ) 872 { 873 return false; 874 } 875 else if ( name.equals( METADATA_FILENAME ) ) 876 { 877 return false; 878 } 879 else if ( new File( dir, name ).isDirectory() ) 880 { 881 return false; 882 } 883 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml 884 else if ( StringUtils.startsWith( name, METADATA_FILENAME_START ) && StringUtils.endsWith( name, ".xml" ) ) 885 { 886 return false; 887 } 888 889 return true; 890 891 } 892 } 893 894 private static final class PomFilenameFilter 895 implements FilenameFilter 896 { 897 898 private final String artifactId, projectVersion; 899 900 private PomFilenameFilter( String artifactId, String projectVersion ) 901 { 902 this.artifactId = artifactId; 903 this.projectVersion = projectVersion; 904 } 905 906 public boolean accept( File dir, String name ) 907 { 908 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) ) 909 { 910 String v = name.substring( artifactId.length() + 1, name.length() - 4 ); 911 v = VersionUtil.getBaseVersion( v ); 912 if ( v.equals( projectVersion ) ) 913 { 914 return true; 915 } 916 } 917 return false; 918 } 919 } 920 921 private static class PomFileFilter 922 implements FilenameFilter 923 { 924 private final String pomFile; 925 926 private PomFileFilter( String pomFile ) 927 { 928 this.pomFile = pomFile; 929 } 930 931 public boolean accept( File dir, String name ) 932 { 933 return pomFile.equals( name ); 934 } 935 } 936 }