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