| package org.apache.archiva.metadata.repository.storage.maven2; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.archiva.checksum.ChecksumAlgorithm; |
| import org.apache.archiva.checksum.ChecksummedFile; |
| import org.apache.archiva.common.Try; |
| import org.apache.archiva.common.utils.VersionUtil; |
| import org.apache.archiva.filter.Filter; |
| import org.apache.archiva.maven2.metadata.MavenMetadataReader; |
| import org.apache.archiva.metadata.model.ArtifactMetadata; |
| import org.apache.archiva.metadata.model.ProjectMetadata; |
| import org.apache.archiva.metadata.model.ProjectVersionMetadata; |
| import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet; |
| import org.apache.archiva.metadata.repository.storage.*; |
| import org.apache.archiva.model.ArchivaRepositoryMetadata; |
| import org.apache.archiva.model.ArtifactReference; |
| import org.apache.archiva.model.SnapshotVersion; |
| import org.apache.archiva.policies.ProxyDownloadException; |
| import org.apache.archiva.proxy.ProxyRegistry; |
| import org.apache.archiva.proxy.maven.WagonFactory; |
| import org.apache.archiva.proxy.model.NetworkProxy; |
| import org.apache.archiva.proxy.model.ProxyConnector; |
| import org.apache.archiva.proxy.model.RepositoryProxyHandler; |
| import org.apache.archiva.repository.*; |
| import org.apache.archiva.repository.content.PathParser; |
| import org.apache.archiva.repository.maven2.MavenSystemManager; |
| import org.apache.archiva.repository.metadata.RepositoryMetadataException; |
| import org.apache.archiva.repository.storage.StorageAsset; |
| import org.apache.archiva.xml.XMLException; |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.maven.model.*; |
| import org.apache.maven.model.building.*; |
| import org.apache.maven.model.io.xpp3.MavenXpp3Reader; |
| import org.codehaus.plexus.util.xml.pull.XmlPullParserException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.context.ApplicationContext; |
| import org.springframework.stereotype.Service; |
| |
| import javax.annotation.PostConstruct; |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.nio.channels.Channels; |
| import java.nio.charset.Charset; |
| import java.nio.file.NoSuchFileException; |
| import java.time.ZoneId; |
| import java.time.ZonedDateTime; |
| import java.util.*; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| |
| // import java.io.FileNotFoundException; |
| |
| /** |
| * <p> |
| * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to |
| * deal with rather than being instantiated per-repository. |
| * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session). |
| * </p> |
| * <p> |
| * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated |
| * within the session in the context of a single managed repository's resolution needs. |
| * </p> |
| */ |
| @Service("repositoryStorage#maven2") |
| public class Maven2RepositoryStorage |
| implements RepositoryStorage { |
| |
| private static final Logger log = LoggerFactory.getLogger(Maven2RepositoryStorage.class); |
| |
| private ModelBuilder builder; |
| |
| @Inject |
| RepositoryRegistry repositoryRegistry; |
| |
| @Inject |
| @Named( "metadataReader#maven" ) |
| MavenMetadataReader metadataReader; |
| |
| @Inject |
| @Named("repositoryPathTranslator#maven2") |
| private RepositoryPathTranslator pathTranslator; |
| |
| @Inject |
| private WagonFactory wagonFactory; |
| |
| @Inject |
| private ApplicationContext applicationContext; |
| |
| @Inject |
| @Named("pathParser#default") |
| private PathParser pathParser; |
| |
| @Inject |
| private ProxyRegistry proxyRegistry; |
| |
| @Inject |
| private MavenSystemManager mavenSystemManager; |
| |
| private static final String METADATA_FILENAME_START = "maven-metadata"; |
| |
| private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml"; |
| |
| // This array must be lexically sorted |
| private static final String[] IGNORED_FILES = {METADATA_FILENAME, "resolver-status.properties"}; |
| |
| private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader(); |
| |
| |
| @PostConstruct |
| public void initialize() { |
| builder = new DefaultModelBuilderFactory().newInstance(); |
| |
| } |
| |
| @Override |
| public ProjectMetadata readProjectMetadata(String repoId, String namespace, String projectId) { |
| // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there? |
| return null; |
| } |
| |
| @Override |
| public ProjectVersionMetadata readProjectVersionMetadata(ReadMetadataRequest readMetadataRequest) |
| throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException, |
| RepositoryStorageRuntimeException { |
| |
| ManagedRepository managedRepository = repositoryRegistry.getManagedRepository(readMetadataRequest.getRepositoryId()); |
| boolean isReleases = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.RELEASE); |
| boolean isSnapshots = managedRepository.getActiveReleaseSchemes().contains(ReleaseScheme.SNAPSHOT); |
| String artifactVersion = readMetadataRequest.getProjectVersion(); |
| // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo) |
| if (!readMetadataRequest.isBrowsingRequest()) { |
| if (VersionUtil.isSnapshot(artifactVersion)) { |
| // skygo trying to improve speed by honoring managed configuration MRM-1658 |
| if (isReleases && !isSnapshots) { |
| throw new RepositoryStorageRuntimeException("lookforsnaponreleaseonly", |
| "managed repo is configured for release only"); |
| } |
| } else { |
| if (!isReleases && isSnapshots) { |
| throw new RepositoryStorageRuntimeException("lookforsreleaseonsneponly", |
| "managed repo is configured for snapshot only"); |
| } |
| } |
| } |
| StorageAsset basedir = managedRepository.getAsset(""); |
| if (VersionUtil.isSnapshot(artifactVersion)) { |
| StorageAsset metadataFile = pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), |
| readMetadataRequest.getProjectId(), artifactVersion, |
| METADATA_FILENAME); |
| try { |
| ArchivaRepositoryMetadata metadata = metadataReader.read(metadataFile); |
| |
| // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename |
| SnapshotVersion snapshotVersion = metadata.getSnapshotVersion(); |
| if (snapshotVersion != null) { |
| artifactVersion = |
| artifactVersion.substring(0, artifactVersion.length() - 8); // remove SNAPSHOT from end |
| artifactVersion = |
| artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber(); |
| } |
| } catch ( RepositoryMetadataException e) { |
| // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version |
| log.warn("Invalid metadata: {} - {}", metadataFile, e.getMessage()); |
| } |
| } |
| |
| // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator |
| String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom"; |
| StorageAsset file = |
| pathTranslator.toFile(basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(), |
| readMetadataRequest.getProjectVersion(), id); |
| |
| if (!file.exists()) { |
| // metadata could not be resolved |
| throw new RepositoryStorageMetadataNotFoundException( |
| "The artifact's POM file '" + file.getPath() + "' was missing"); |
| } |
| |
| // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache |
| // anything locally! |
| List<RemoteRepository> remoteRepositories = new ArrayList<>(); |
| Map<String, NetworkProxy> networkProxies = new HashMap<>(); |
| |
| Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyRegistry.getProxyConnectorAsMap(); |
| List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get(readMetadataRequest.getRepositoryId()); |
| if (proxyConnectors != null) { |
| for (ProxyConnector proxyConnector : proxyConnectors) { |
| RemoteRepository remoteRepoConfig = |
| repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepository().getId()); |
| |
| if (remoteRepoConfig != null) { |
| remoteRepositories.add(remoteRepoConfig); |
| |
| NetworkProxy networkProxyConfig = |
| proxyRegistry.getNetworkProxy(proxyConnector.getProxyId()); |
| |
| if (networkProxyConfig != null) { |
| // key/value: remote repo ID/proxy info |
| networkProxies.put(proxyConnector.getTargetRepository().getId(), networkProxyConfig); |
| } |
| } |
| } |
| } |
| |
| // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which |
| // can have released parent pom |
| if (readMetadataRequest.isBrowsingRequest()) { |
| remoteRepositories.addAll(repositoryRegistry.getRemoteRepositories()); |
| } |
| |
| ModelBuildingRequest req = |
| new DefaultModelBuildingRequest().setProcessPlugins(false).setPomFile(file.getFilePath().toFile()).setTwoPhaseBuilding( |
| false).setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); |
| |
| //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm |
| req.setSystemProperties(System.getProperties()); |
| |
| // MRM-1411 |
| req.setModelResolver( |
| new RepositoryModelResolver(managedRepository, pathTranslator, wagonFactory, remoteRepositories, |
| networkProxies, managedRepository, mavenSystemManager, metadataReader)); |
| |
| Model model; |
| try { |
| model = builder.build(req).getEffectiveModel(); |
| } catch (ModelBuildingException e) { |
| String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage(); |
| |
| List<ModelProblem> modelProblems = e.getProblems(); |
| for (ModelProblem problem : modelProblems) { |
| // MRM-1411, related to MRM-1335 |
| // this means that the problem was that the parent wasn't resolved! |
| // olamy really hackhish but fail with java profile so use error message |
| // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) ) |
| // but setTwoPhaseBuilding(true) fix that |
| if (((problem.getException() instanceof FileNotFoundException |
| || problem.getException() instanceof NoSuchFileException |
| ) && e.getModelId() != null && |
| !e.getModelId().equals(problem.getModelId()))) { |
| log.warn("The artifact's parent POM file '{}' cannot be resolved. " |
| + "Using defaults for project version metadata..", file); |
| |
| ProjectVersionMetadata metadata = new ProjectVersionMetadata(); |
| metadata.setId(readMetadataRequest.getProjectVersion()); |
| |
| MavenProjectFacet facet = new MavenProjectFacet(); |
| facet.setGroupId(readMetadataRequest.getNamespace()); |
| facet.setArtifactId(readMetadataRequest.getProjectId()); |
| facet.setPackaging("jar"); |
| metadata.addFacet(facet); |
| |
| String errMsg = |
| "Error in resolving artifact's parent POM file. " + (problem.getException() == null |
| ? problem.getMessage() |
| : problem.getException().getMessage()); |
| RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet(); |
| repoProblemFacet.setRepositoryId(readMetadataRequest.getRepositoryId()); |
| repoProblemFacet.setId(readMetadataRequest.getRepositoryId()); |
| repoProblemFacet.setMessage(errMsg); |
| repoProblemFacet.setProblem(errMsg); |
| repoProblemFacet.setProject(readMetadataRequest.getProjectId()); |
| repoProblemFacet.setVersion(readMetadataRequest.getProjectVersion()); |
| repoProblemFacet.setNamespace(readMetadataRequest.getNamespace()); |
| |
| metadata.addFacet(repoProblemFacet); |
| |
| return metadata; |
| } |
| } |
| |
| throw new RepositoryStorageMetadataInvalidException("invalid-pom", msg, e); |
| } |
| |
| // Check if the POM is in the correct location |
| boolean correctGroupId = readMetadataRequest.getNamespace().equals(model.getGroupId()); |
| boolean correctArtifactId = readMetadataRequest.getProjectId().equals(model.getArtifactId()); |
| boolean correctVersion = readMetadataRequest.getProjectVersion().equals(model.getVersion()); |
| if (!correctGroupId || !correctArtifactId || !correctVersion) { |
| StringBuilder message = new StringBuilder("Incorrect POM coordinates in '" + file + "':"); |
| if (!correctGroupId) { |
| message.append("\nIncorrect group ID: ").append(model.getGroupId()); |
| } |
| if (!correctArtifactId) { |
| message.append("\nIncorrect artifact ID: ").append(model.getArtifactId()); |
| } |
| if (!correctVersion) { |
| message.append("\nIncorrect version: ").append(model.getVersion()); |
| } |
| |
| throw new RepositoryStorageMetadataInvalidException("mislocated-pom", message.toString()); |
| } |
| |
| ProjectVersionMetadata metadata = new ProjectVersionMetadata(); |
| metadata.setCiManagement(convertCiManagement(model.getCiManagement())); |
| metadata.setDescription(model.getDescription()); |
| metadata.setId(readMetadataRequest.getProjectVersion()); |
| metadata.setIssueManagement(convertIssueManagement(model.getIssueManagement())); |
| metadata.setLicenses(convertLicenses(model.getLicenses())); |
| metadata.setMailingLists(convertMailingLists(model.getMailingLists())); |
| metadata.setDependencies(convertDependencies(model.getDependencies())); |
| metadata.setName(model.getName()); |
| metadata.setOrganization(convertOrganization(model.getOrganization())); |
| metadata.setScm(convertScm(model.getScm())); |
| metadata.setUrl(model.getUrl()); |
| metadata.setProperties(model.getProperties()); |
| |
| MavenProjectFacet facet = new MavenProjectFacet(); |
| facet.setGroupId(model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId()); |
| facet.setArtifactId(model.getArtifactId()); |
| facet.setPackaging(model.getPackaging()); |
| if (model.getParent() != null) { |
| MavenProjectParent parent = new MavenProjectParent(); |
| parent.setGroupId(model.getParent().getGroupId()); |
| parent.setArtifactId(model.getParent().getArtifactId()); |
| parent.setVersion(model.getParent().getVersion()); |
| facet.setParent(parent); |
| } |
| metadata.addFacet(facet); |
| |
| return metadata; |
| |
| |
| } |
| |
| public void setWagonFactory(WagonFactory wagonFactory) { |
| this.wagonFactory = wagonFactory; |
| } |
| |
| private List<org.apache.archiva.metadata.model.Dependency> convertDependencies(List<Dependency> dependencies) { |
| List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>(); |
| for (Dependency dependency : dependencies) { |
| org.apache.archiva.metadata.model.Dependency newDependency = |
| new org.apache.archiva.metadata.model.Dependency(); |
| newDependency.setArtifactId(dependency.getArtifactId()); |
| newDependency.setClassifier(dependency.getClassifier()); |
| newDependency.setNamespace(dependency.getGroupId()); |
| newDependency.setOptional(dependency.isOptional()); |
| newDependency.setScope(dependency.getScope()); |
| newDependency.setSystemPath(dependency.getSystemPath()); |
| newDependency.setType(dependency.getType()); |
| newDependency.setVersion(dependency.getVersion()); |
| l.add(newDependency); |
| } |
| return l; |
| } |
| |
| private org.apache.archiva.metadata.model.Scm convertScm(Scm scm) { |
| org.apache.archiva.metadata.model.Scm newScm = null; |
| if (scm != null) { |
| newScm = new org.apache.archiva.metadata.model.Scm(); |
| newScm.setConnection(scm.getConnection()); |
| newScm.setDeveloperConnection(scm.getDeveloperConnection()); |
| newScm.setUrl(scm.getUrl()); |
| } |
| return newScm; |
| } |
| |
| private org.apache.archiva.metadata.model.Organization convertOrganization(Organization organization) { |
| org.apache.archiva.metadata.model.Organization org = null; |
| if (organization != null) { |
| org = new org.apache.archiva.metadata.model.Organization(); |
| org.setName(organization.getName()); |
| org.setUrl(organization.getUrl()); |
| } |
| return org; |
| } |
| |
| private List<org.apache.archiva.metadata.model.License> convertLicenses(List<License> licenses) { |
| List<org.apache.archiva.metadata.model.License> l = new ArrayList<>(); |
| for (License license : licenses) { |
| org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License(); |
| newLicense.setName(license.getName()); |
| newLicense.setUrl(license.getUrl()); |
| l.add(newLicense); |
| } |
| return l; |
| } |
| |
| private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists(List<MailingList> mailingLists) { |
| List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>(); |
| for (MailingList mailingList : mailingLists) { |
| org.apache.archiva.metadata.model.MailingList newMailingList = |
| new org.apache.archiva.metadata.model.MailingList(); |
| newMailingList.setName(mailingList.getName()); |
| newMailingList.setMainArchiveUrl(mailingList.getArchive()); |
| newMailingList.setPostAddress(mailingList.getPost()); |
| newMailingList.setSubscribeAddress(mailingList.getSubscribe()); |
| newMailingList.setUnsubscribeAddress(mailingList.getUnsubscribe()); |
| newMailingList.setOtherArchives(mailingList.getOtherArchives()); |
| l.add(newMailingList); |
| } |
| return l; |
| } |
| |
| private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement(IssueManagement issueManagement) { |
| org.apache.archiva.metadata.model.IssueManagement im = null; |
| if (issueManagement != null) { |
| im = new org.apache.archiva.metadata.model.IssueManagement(); |
| im.setSystem(issueManagement.getSystem()); |
| im.setUrl(issueManagement.getUrl()); |
| } |
| return im; |
| } |
| |
| private org.apache.archiva.metadata.model.CiManagement convertCiManagement(CiManagement ciManagement) { |
| org.apache.archiva.metadata.model.CiManagement ci = null; |
| if (ciManagement != null) { |
| ci = new org.apache.archiva.metadata.model.CiManagement(); |
| ci.setSystem(ciManagement.getSystem()); |
| ci.setUrl(ciManagement.getUrl()); |
| } |
| return ci; |
| } |
| |
| @Override |
| public Collection<String> listRootNamespaces(String repoId, Filter<String> filter) |
| throws RepositoryStorageRuntimeException { |
| StorageAsset dir = getRepositoryBasedir(repoId); |
| |
| return getSortedFiles(dir, filter); |
| } |
| |
| private static Collection<String> getSortedFiles(StorageAsset dir, Filter<String> filter) { |
| |
| final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter); |
| return dir.list().stream().filter(f -> f.isContainer()) |
| .filter(dFilter) |
| .map(path -> path.getName().toString()) |
| .sorted().collect(Collectors.toList()); |
| |
| } |
| |
| private StorageAsset getRepositoryBasedir(String repoId) |
| throws RepositoryStorageRuntimeException { |
| ManagedRepository repositoryConfiguration = repositoryRegistry.getManagedRepository(repoId); |
| |
| return repositoryConfiguration.getAsset(""); |
| } |
| |
| @Override |
| public Collection<String> listNamespaces(String repoId, String namespace, Filter<String> filter) |
| throws RepositoryStorageRuntimeException { |
| StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace); |
| if (!(dir.exists()) && !dir.isContainer()) { |
| return Collections.emptyList(); |
| } |
| // scan all the directories which are potential namespaces. Any directories known to be projects are excluded |
| Predicate<StorageAsset> dFilter = new DirectoryFilter(filter); |
| return dir.list().stream().filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getName().toString()) |
| .sorted().collect(Collectors.toList()); |
| } |
| |
| @Override |
| public Collection<String> listProjects(String repoId, String namespace, Filter<String> filter) |
| throws RepositoryStorageRuntimeException { |
| StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace); |
| if (!(dir.exists() && dir.isContainer())) { |
| return Collections.emptyList(); |
| } |
| // scan all directories in the namespace, and only include those that are known to be projects |
| final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter); |
| return dir.list().stream().filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getName().toString()) |
| .sorted().collect(Collectors.toList()); |
| |
| } |
| |
| @Override |
| public Collection<String> listProjectVersions(String repoId, String namespace, String projectId, |
| Filter<String> filter) |
| throws RepositoryStorageRuntimeException { |
| StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(repoId), namespace, projectId); |
| if (!(dir.exists() && dir.isContainer())) { |
| return Collections.emptyList(); |
| } |
| |
| // all directories in a project directory can be considered a version |
| return getSortedFiles(dir, filter); |
| } |
| |
| @Override |
| public Collection<ArtifactMetadata> readArtifactsMetadata(ReadMetadataRequest readMetadataRequest) |
| throws RepositoryStorageRuntimeException { |
| StorageAsset dir = pathTranslator.toFile(getRepositoryBasedir(readMetadataRequest.getRepositoryId()), |
| readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(), |
| readMetadataRequest.getProjectVersion()); |
| if (!(dir.exists() && dir.isContainer())) { |
| return Collections.emptyList(); |
| } |
| |
| // all files that are not metadata and not a checksum / signature are considered artifacts |
| final Predicate<StorageAsset> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter()); |
| // Returns a map TRUE -> (success values), FALSE -> (Exceptions) |
| Map<Boolean, List<Try<ArtifactMetadata>>> result = dir.list().stream().filter(dFilter).map(path -> { |
| try { |
| return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(), |
| readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(), |
| path)); |
| } catch (Exception e) { |
| log.debug("Could not create metadata for {}: {}", path, e.getMessage(), e); |
| return Try.<ArtifactMetadata>failure(e); |
| } |
| } |
| ).collect(Collectors.groupingBy(Try::isSuccess)); |
| if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size() > 0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size() == 0)) { |
| log.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size()); |
| Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0); |
| log.error("Sample exception {}", failure.getError().getMessage(), failure.getError()); |
| throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files"); |
| } else { |
| if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) { |
| return Collections.emptyList(); |
| } |
| return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList()); |
| } |
| |
| } |
| |
| @Override |
| public ArtifactMetadata readArtifactMetadataFromPath(String repoId, String path) |
| throws RepositoryStorageRuntimeException { |
| ArtifactMetadata metadata = pathTranslator.getArtifactForPath(repoId, path); |
| |
| try { |
| populateArtifactMetadataFromFile(metadata, getRepositoryBasedir(repoId).resolve(path)); |
| } catch (IOException e) { |
| throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of " + path + " :" + e.getMessage(), e); |
| } |
| |
| return metadata; |
| } |
| |
| private ArtifactMetadata getArtifactFromFile(String repoId, String namespace, String projectId, |
| String projectVersion, StorageAsset file) throws IOException { |
| ArtifactMetadata metadata = |
| pathTranslator.getArtifactFromId(repoId, namespace, projectId, projectVersion, file.getName()); |
| |
| populateArtifactMetadataFromFile(metadata, file); |
| |
| return metadata; |
| } |
| |
| @Override |
| public void applyServerSideRelocation(ManagedRepository managedRepository, ArtifactReference artifact) |
| throws ProxyDownloadException { |
| if ("pom".equals(artifact.getType())) { |
| return; |
| } |
| |
| // Build the artifact POM reference |
| ArtifactReference pomReference = new ArtifactReference(); |
| pomReference.setGroupId(artifact.getGroupId()); |
| pomReference.setArtifactId(artifact.getArtifactId()); |
| pomReference.setVersion(artifact.getVersion()); |
| pomReference.setType("pom"); |
| |
| RepositoryType repositoryType = managedRepository.getType(); |
| if (!proxyRegistry.hasHandler(repositoryType)) { |
| throw new ProxyDownloadException("No proxy handler found for repository type " + repositoryType, new HashMap<>()); |
| } |
| |
| RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(repositoryType).get(0); |
| |
| // Get the artifact POM from proxied repositories if needed |
| proxyHandler.fetchFromProxies(managedRepository, pomReference); |
| |
| // Open and read the POM from the managed repo |
| StorageAsset pom = managedRepository.getContent().toFile(pomReference); |
| |
| if (!pom.exists()) { |
| return; |
| } |
| |
| try { |
| // MavenXpp3Reader leaves the file open, so we need to close it ourselves. |
| |
| Model model; |
| try (Reader reader = Channels.newReader(pom.getReadChannel(), Charset.defaultCharset().name())) { |
| model = MAVEN_XPP_3_READER.read(reader); |
| } |
| |
| DistributionManagement dist = model.getDistributionManagement(); |
| if (dist != null) { |
| Relocation relocation = dist.getRelocation(); |
| if (relocation != null) { |
| // artifact is relocated : update the repositoryPath |
| if (relocation.getGroupId() != null) { |
| artifact.setGroupId(relocation.getGroupId()); |
| } |
| if (relocation.getArtifactId() != null) { |
| artifact.setArtifactId(relocation.getArtifactId()); |
| } |
| if (relocation.getVersion() != null) { |
| artifact.setVersion(relocation.getVersion()); |
| } |
| } |
| } |
| } catch (IOException e) { |
| // Unable to read POM : ignore. |
| } catch (XmlPullParserException e) { |
| // Invalid POM : ignore |
| } |
| } |
| |
| |
| @Override |
| public String getFilePath(String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository) { |
| // managedRepository can be null |
| // extract artifact reference from url |
| // groupId:artifactId:version:packaging:classifier |
| //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar |
| String logicalResource = null; |
| String requestPathInfo = StringUtils.defaultString(requestPath); |
| |
| //remove prefix ie /repository/blah becomes /blah |
| requestPathInfo = removePrefix(requestPathInfo); |
| |
| // Remove prefixing slash as the repository id doesn't contain it; |
| if (requestPathInfo.startsWith("/")) { |
| requestPathInfo = requestPathInfo.substring(1); |
| } |
| |
| int slash = requestPathInfo.indexOf('/'); |
| if (slash > 0) { |
| logicalResource = requestPathInfo.substring(slash); |
| |
| if (logicalResource.endsWith("/..")) { |
| logicalResource += "/"; |
| } |
| |
| if (logicalResource != null && logicalResource.startsWith("//")) { |
| logicalResource = logicalResource.substring(1); |
| } |
| |
| if (logicalResource == null) { |
| logicalResource = "/"; |
| } |
| } else { |
| logicalResource = "/"; |
| } |
| return logicalResource; |
| |
| } |
| |
| @Override |
| public String getFilePathWithVersion(final String requestPath, ManagedRepositoryContent managedRepositoryContent) |
| throws RelocationException |
| { |
| |
| if (StringUtils.endsWith(requestPath, METADATA_FILENAME)) { |
| return getFilePath(requestPath, managedRepositoryContent.getRepository()); |
| } |
| |
| String filePath = getFilePath(requestPath, managedRepositoryContent.getRepository()); |
| |
| ArtifactReference artifactReference = null; |
| try { |
| artifactReference = pathParser.toArtifactReference(filePath); |
| } catch (LayoutException e) { |
| return filePath; |
| } |
| |
| if (StringUtils.endsWith(artifactReference.getVersion(), VersionUtil.SNAPSHOT)) { |
| // read maven metadata to get last timestamp |
| StorageAsset metadataDir = managedRepositoryContent.getRepository().getAsset(filePath).getParent(); |
| if (!metadataDir.exists()) { |
| return filePath; |
| } |
| StorageAsset metadataFile = metadataDir.resolve(METADATA_FILENAME); |
| if (!metadataFile.exists()) { |
| return filePath; |
| } |
| ArchivaRepositoryMetadata archivaRepositoryMetadata = null; |
| try |
| { |
| archivaRepositoryMetadata = metadataReader.read(metadataFile); |
| } |
| catch ( RepositoryMetadataException e ) |
| { |
| log.error( "Could not read metadata {}", e.getMessage( ), e ); |
| return filePath; |
| } |
| int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber(); |
| String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp(); |
| |
| // MRM-1846 |
| if (buildNumber < 1 && timestamp == null) { |
| return filePath; |
| } |
| |
| // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar |
| // -> archiva-checksum-1.4-M4-20130425.081822-1.jar |
| |
| filePath = StringUtils.replace(filePath, // |
| artifactReference.getArtifactId() // |
| + "-" + artifactReference.getVersion(), // |
| artifactReference.getArtifactId() // |
| + "-" + StringUtils.remove(artifactReference.getVersion(), |
| "-" + VersionUtil.SNAPSHOT) // |
| + "-" + timestamp // |
| + "-" + buildNumber); |
| |
| throw new RelocationException("/repository/" + managedRepositoryContent.getRepository().getId() + |
| (StringUtils.startsWith(filePath, "/") ? "" : "/") + filePath, |
| RelocationException.RelocationType.TEMPORARY); |
| |
| } |
| |
| return filePath; |
| } |
| |
| //----------------------------- |
| // internal |
| //----------------------------- |
| |
| /** |
| * FIXME remove |
| * |
| * @param href |
| * @return |
| */ |
| private static String removePrefix(final String href) { |
| String[] parts = StringUtils.split(href, '/'); |
| parts = (String[]) ArrayUtils.subarray(parts, 1, parts.length); |
| if (parts == null || parts.length == 0) { |
| return "/"; |
| } |
| |
| String joinedString = StringUtils.join(parts, '/'); |
| if (href.endsWith("/")) { |
| joinedString = joinedString + "/"; |
| } |
| |
| return joinedString; |
| } |
| |
| private static void populateArtifactMetadataFromFile(ArtifactMetadata metadata, StorageAsset file) throws IOException { |
| metadata.setWhenGathered(ZonedDateTime.now(ZoneId.of("GMT"))); |
| metadata.setFileLastModified(file.getModificationTime().toEpochMilli()); |
| ChecksummedFile checksummedFile = new ChecksummedFile(file.getFilePath()); |
| try { |
| metadata.setMd5(checksummedFile.calculateChecksum(ChecksumAlgorithm.MD5)); |
| } catch (IOException e) { |
| log.error("Unable to checksum file {}: {},MD5", file, e.getMessage()); |
| } |
| try { |
| metadata.setSha1(checksummedFile.calculateChecksum(ChecksumAlgorithm.SHA1)); |
| } catch (IOException e) { |
| log.error("Unable to checksum file {}: {},SHA1", file, e.getMessage()); |
| } |
| metadata.setSize(file.getSize()); |
| } |
| |
| private boolean isProject(StorageAsset dir, Filter<String> filter) { |
| // scan directories for a valid project version subdirectory, meaning this must be a project directory |
| final Predicate<StorageAsset> dFilter = new DirectoryFilter(filter); |
| boolean projFound = dir.list().stream().filter(dFilter) |
| .anyMatch(path -> isProjectVersion(path)); |
| if (projFound) { |
| return true; |
| } |
| |
| // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project |
| ArchivaRepositoryMetadata metadata = readMetadata(dir); |
| if (metadata != null && dir.getName().toString().equals(metadata.getArtifactId())) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private boolean isProjectVersion(StorageAsset dir) { |
| final String artifactId = dir.getParent().getName(); |
| final String projectVersion = dir.getName(); |
| |
| // check if there is a POM artifact file to ensure it is a version directory |
| |
| Predicate<StorageAsset> filter; |
| if (VersionUtil.isSnapshot(projectVersion)) { |
| filter = new PomFilenameFilter(artifactId, projectVersion); |
| } else { |
| final String pomFile = artifactId + "-" + projectVersion + ".pom"; |
| filter = new PomFileFilter(pomFile); |
| } |
| if (dir.list().stream().filter(f -> !f.isContainer()).anyMatch(filter)) { |
| return true; |
| } |
| // if a metadata file is present, check if this is the "version" directory, marking it as a project version |
| ArchivaRepositoryMetadata metadata = readMetadata(dir); |
| if (metadata != null && projectVersion.equals(metadata.getVersion())) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private ArchivaRepositoryMetadata readMetadata(StorageAsset directory) { |
| ArchivaRepositoryMetadata metadata = null; |
| StorageAsset metadataFile = directory.resolve(METADATA_FILENAME); |
| if (metadataFile.exists()) { |
| try { |
| metadata = metadataReader.read(metadataFile); |
| } catch ( RepositoryMetadataException e ) |
| { |
| // Ignore missing or invalid metadata |
| } |
| } |
| return metadata; |
| } |
| |
| private static class DirectoryFilter |
| implements Predicate<StorageAsset> { |
| private final Filter<String> filter; |
| |
| public DirectoryFilter(Filter<String> filter) { |
| this.filter = filter; |
| } |
| |
| @Override |
| public boolean test(StorageAsset dir) { |
| final String name = dir.getName(); |
| if (!filter.accept(name)) { |
| return false; |
| } else if (name.startsWith(".")) { |
| return false; |
| } else if (!dir.isContainer()) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private static class ArtifactDirectoryFilter |
| implements Predicate<StorageAsset> { |
| private final Filter<String> filter; |
| |
| private ArtifactDirectoryFilter(Filter<String> filter) { |
| this.filter = filter; |
| } |
| |
| @Override |
| public boolean test(StorageAsset file) { |
| final Set<String> checksumExts = ChecksumAlgorithm.getAllExtensions(); |
| final String path = file.getPath(); |
| final String name = file.getName(); |
| final String extension = StringUtils.substringAfterLast(name, ".").toLowerCase(); |
| // TODO compare to logic in maven-repository-layer |
| if (file.isContainer()) { |
| return false; |
| } else if (!filter.accept(name)) { |
| return false; |
| } else if (name.startsWith(".") || path.contains("/.") ) { |
| return false; |
| } else if (checksumExts.contains(extension)) { |
| return false; |
| } else if (Arrays.binarySearch(IGNORED_FILES, name) >= 0) { |
| return false; |
| } |
| // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml |
| else if (StringUtils.startsWith(name, METADATA_FILENAME_START) && StringUtils.endsWith(name, ".xml")) { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| } |
| |
| |
| private static final class PomFilenameFilter |
| implements Predicate<StorageAsset> { |
| |
| private final String artifactId, projectVersion; |
| |
| private PomFilenameFilter(String artifactId, String projectVersion) { |
| this.artifactId = artifactId; |
| this.projectVersion = projectVersion; |
| } |
| |
| @Override |
| public boolean test(StorageAsset dir) { |
| final String name = dir.getName(); |
| if (name.startsWith(artifactId + "-") && name.endsWith(".pom")) { |
| String v = name.substring(artifactId.length() + 1, name.length() - 4); |
| v = VersionUtil.getBaseVersion(v); |
| if (v.equals(projectVersion)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } |
| |
| private static class PomFileFilter |
| implements Predicate<StorageAsset> { |
| private final String pomFile; |
| |
| private PomFileFilter(String pomFile) { |
| this.pomFile = pomFile; |
| } |
| |
| @Override |
| public boolean test(StorageAsset dir) { |
| return pomFile.equals(dir.getName()); |
| } |
| } |
| |
| |
| public PathParser getPathParser() { |
| return pathParser; |
| } |
| |
| public void setPathParser(PathParser pathParser) { |
| this.pathParser = pathParser; |
| } |
| } |