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