This project has retired. For details please refer to its Attic page.
Source code
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}