| 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.admin.model.RepositoryAdminException; |
| import org.apache.archiva.admin.model.beans.ManagedRepository; |
| import org.apache.archiva.admin.model.beans.NetworkProxy; |
| import org.apache.archiva.admin.model.beans.ProxyConnector; |
| import org.apache.archiva.admin.model.beans.RemoteRepository; |
| import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; |
| import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin; |
| import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin; |
| import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin; |
| import org.apache.archiva.checksum.ChecksumAlgorithm; |
| import org.apache.archiva.checksum.ChecksummedFile; |
| import org.apache.archiva.common.utils.VersionUtil; |
| 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.filter.Filter; |
| import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest; |
| import org.apache.archiva.metadata.repository.storage.RelocationException; |
| import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator; |
| import org.apache.archiva.metadata.repository.storage.RepositoryStorage; |
| import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException; |
| import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException; |
| import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException; |
| 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.common.WagonFactory; |
| import org.apache.archiva.proxy.model.RepositoryProxyConnectors; |
| import org.apache.archiva.repository.ManagedRepositoryContent; |
| import org.apache.archiva.repository.content.PathParser; |
| import org.apache.archiva.repository.layout.LayoutException; |
| import org.apache.archiva.xml.XMLException; |
| import org.apache.commons.lang.ArrayUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.maven.model.CiManagement; |
| import org.apache.maven.model.Dependency; |
| import org.apache.maven.model.DistributionManagement; |
| import org.apache.maven.model.IssueManagement; |
| import org.apache.maven.model.License; |
| import org.apache.maven.model.MailingList; |
| import org.apache.maven.model.Model; |
| import org.apache.maven.model.Organization; |
| import org.apache.maven.model.Relocation; |
| import org.apache.maven.model.Scm; |
| import org.apache.maven.model.building.DefaultModelBuilderFactory; |
| import org.apache.maven.model.building.DefaultModelBuildingRequest; |
| import org.apache.maven.model.building.ModelBuilder; |
| import org.apache.maven.model.building.ModelBuildingException; |
| import org.apache.maven.model.building.ModelBuildingRequest; |
| import org.apache.maven.model.building.ModelProblem; |
| 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.File; |
| import java.io.FileNotFoundException; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.nio.charset.Charset; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * <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 LOGGER = LoggerFactory.getLogger( Maven2RepositoryStorage.class ); |
| |
| private ModelBuilder builder; |
| |
| @Inject |
| private RemoteRepositoryAdmin remoteRepositoryAdmin; |
| |
| @Inject |
| private ManagedRepositoryAdmin managedRepositoryAdmin; |
| |
| @Inject |
| private ProxyConnectorAdmin proxyConnectorAdmin; |
| |
| @Inject |
| private NetworkProxyAdmin networkProxyAdmin; |
| |
| @Inject |
| @Named( "repositoryPathTranslator#maven2" ) |
| private RepositoryPathTranslator pathTranslator; |
| |
| @Inject |
| private WagonFactory wagonFactory; |
| |
| @Inject |
| private ApplicationContext applicationContext; |
| |
| @Inject |
| @Named( "pathParser#default" ) |
| private PathParser pathParser; |
| |
| private static final String METADATA_FILENAME_START = "maven-metadata"; |
| |
| private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml"; |
| |
| 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 |
| { |
| try |
| { |
| ManagedRepository managedRepository = |
| managedRepositoryAdmin.getManagedRepository( readMetadataRequest.getRepositoryId() ); |
| 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 ( managedRepository.isReleases() && !managedRepository.isSnapshots() ) |
| { |
| throw new RepositoryStorageRuntimeException( "lookforsnaponreleaseonly", |
| "managed repo is configured for release only" ); |
| } |
| } |
| else |
| { |
| if ( !managedRepository.isReleases() && managedRepository.isSnapshots() ) |
| { |
| throw new RepositoryStorageRuntimeException( "lookforsreleaseonsneponly", |
| "managed repo is configured for snapshot only" ); |
| } |
| } |
| } |
| File basedir = new File( managedRepository.getLocation() ); |
| if ( VersionUtil.isSnapshot( artifactVersion ) ) |
| { |
| File metadataFile = pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), |
| readMetadataRequest.getProjectId(), artifactVersion, |
| METADATA_FILENAME ); |
| try |
| { |
| ArchivaRepositoryMetadata metadata = MavenMetadataReader.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 ( XMLException e ) |
| { |
| // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version |
| LOGGER.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"; |
| File 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.getAbsolutePath() + "' 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 = proxyConnectorAdmin.getProxyConnectorAsMap(); |
| List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get( readMetadataRequest.getRepositoryId() ); |
| if ( proxyConnectors != null ) |
| { |
| for ( ProxyConnector proxyConnector : proxyConnectors ) |
| { |
| RemoteRepository remoteRepoConfig = |
| remoteRepositoryAdmin.getRemoteRepository( proxyConnector.getTargetRepoId() ); |
| |
| if ( remoteRepoConfig != null ) |
| { |
| remoteRepositories.add( remoteRepoConfig ); |
| |
| NetworkProxy networkProxyConfig = |
| networkProxyAdmin.getNetworkProxy( proxyConnector.getProxyId() ); |
| |
| if ( networkProxyConfig != null ) |
| { |
| // key/value: remote repo ID/proxy info |
| networkProxies.put( proxyConnector.getTargetRepoId(), 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( remoteRepositoryAdmin.getRemoteRepositories() ); |
| } |
| |
| ModelBuildingRequest req = |
| new DefaultModelBuildingRequest().setProcessPlugins( false ).setPomFile( file ).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 ) ); |
| |
| 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 && e.getModelId() != null && |
| !e.getModelId().equals( problem.getModelId() ) ) ) |
| { |
| LOGGER.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; |
| } |
| catch ( RepositoryAdminException e ) |
| { |
| throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e ); |
| } |
| } |
| |
| 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.setGroupId( 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 |
| { |
| File dir = getRepositoryBasedir( repoId ); |
| |
| return getSortedFiles( dir, filter ); |
| } |
| |
| private static Collection<String> getSortedFiles( File dir, Filter<String> filter ) |
| { |
| List<String> fileNames; |
| String[] files = dir.list( new DirectoryFilter( filter ) ); |
| if ( files != null ) |
| { |
| fileNames = new ArrayList<>( Arrays.asList( files ) ); |
| Collections.sort( fileNames ); |
| } |
| else |
| { |
| fileNames = Collections.emptyList(); |
| } |
| return fileNames; |
| } |
| |
| private File getRepositoryBasedir( String repoId ) |
| throws RepositoryStorageRuntimeException |
| { |
| try |
| { |
| ManagedRepository repositoryConfiguration = managedRepositoryAdmin.getManagedRepository( repoId ); |
| |
| return new File( repositoryConfiguration.getLocation() ); |
| } |
| catch ( RepositoryAdminException e ) |
| { |
| throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e ); |
| } |
| } |
| |
| @Override |
| public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter ) |
| throws RepositoryStorageRuntimeException |
| { |
| File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace ); |
| |
| // scan all the directories which are potential namespaces. Any directories known to be projects are excluded |
| List<String> namespaces = new ArrayList<>(); |
| File[] files = dir.listFiles( new DirectoryFilter( filter ) ); |
| if ( files != null ) |
| { |
| for ( File file : files ) |
| { |
| if ( !isProject( file, filter ) ) |
| { |
| namespaces.add( file.getName() ); |
| } |
| } |
| } |
| Collections.sort( namespaces ); |
| return namespaces; |
| } |
| |
| @Override |
| public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter ) |
| throws RepositoryStorageRuntimeException |
| { |
| File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace ); |
| |
| // scan all directories in the namespace, and only include those that are known to be projects |
| List<String> projects = new ArrayList<>(); |
| |
| File[] files = dir.listFiles( new DirectoryFilter( filter ) ); |
| if ( files != null ) |
| { |
| for ( File file : files ) |
| { |
| if ( isProject( file, filter ) ) |
| { |
| projects.add( file.getName() ); |
| } |
| } |
| } |
| Collections.sort( projects ); |
| return projects; |
| } |
| |
| @Override |
| public Collection<String> listProjectVersions( String repoId, String namespace, String projectId, |
| Filter<String> filter ) |
| throws RepositoryStorageRuntimeException |
| { |
| File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId ); |
| |
| // all directories in a project directory can be considered a version |
| return getSortedFiles( dir, filter ); |
| } |
| |
| @Override |
| public Collection<ArtifactMetadata> readArtifactsMetadata( ReadMetadataRequest readMetadataRequest ) |
| throws RepositoryStorageRuntimeException |
| { |
| File dir = pathTranslator.toFile( getRepositoryBasedir( readMetadataRequest.getRepositoryId() ), |
| readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(), |
| readMetadataRequest.getProjectVersion() ); |
| |
| // all files that are not metadata and not a checksum / signature are considered artifacts |
| File[] files = dir.listFiles( new ArtifactDirectoryFilter( readMetadataRequest.getFilter() ) ); |
| |
| List<ArtifactMetadata> artifacts = new ArrayList<>(); |
| if ( files != null ) |
| { |
| for ( File file : files ) |
| { |
| ArtifactMetadata metadata = |
| getArtifactFromFile( readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(), |
| readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(), |
| file ); |
| artifacts.add( metadata ); |
| } |
| } |
| return artifacts; |
| } |
| |
| @Override |
| public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path ) |
| throws RepositoryStorageRuntimeException |
| { |
| ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path ); |
| |
| populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) ); |
| |
| return metadata; |
| } |
| |
| private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId, |
| String projectVersion, File file ) |
| { |
| ArtifactMetadata metadata = |
| pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() ); |
| |
| populateArtifactMetadataFromFile( metadata, file ); |
| |
| return metadata; |
| } |
| |
| @Override |
| public void applyServerSideRelocation( ManagedRepositoryContent 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" ); |
| |
| RepositoryProxyConnectors connectors = |
| applicationContext.getBean( "repositoryProxyConnectors#default", RepositoryProxyConnectors.class ); |
| |
| // Get the artifact POM from proxied repositories if needed |
| connectors.fetchFromProxies( managedRepository, pomReference ); |
| |
| // Open and read the POM from the managed repo |
| File pom = managedRepository.toFile( pomReference ); |
| |
| if ( !pom.exists() ) |
| { |
| return; |
| } |
| |
| try |
| { |
| // MavenXpp3Reader leaves the file open, so we need to close it ourselves. |
| |
| Model model = null; |
| try (Reader reader = Files.newBufferedReader( pom.toPath(), Charset.defaultCharset() )) |
| { |
| 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, 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 XMLException, 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 |
| File metadataDir = new File( managedRepositoryContent.getRepoRoot(), filePath ).getParentFile(); |
| if ( !metadataDir.exists() ) |
| { |
| return filePath; |
| } |
| File metadataFile = new File( metadataDir, METADATA_FILENAME ); |
| if ( !metadataFile.exists() ) |
| { |
| return filePath; |
| } |
| ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( metadataFile ); |
| 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, File file ) |
| { |
| metadata.setWhenGathered( new Date() ); |
| metadata.setFileLastModified( file.lastModified() ); |
| ChecksummedFile checksummedFile = new ChecksummedFile( file ); |
| try |
| { |
| metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) ); |
| } |
| catch ( IOException e ) |
| { |
| LOGGER.error( "Unable to checksum file {}: {},MD5", file, e.getMessage() ); |
| } |
| try |
| { |
| metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) ); |
| } |
| catch ( IOException e ) |
| { |
| LOGGER.error( "Unable to checksum file {}: {},SHA1", file, e.getMessage() ); |
| } |
| metadata.setSize( file.length() ); |
| } |
| |
| private boolean isProject( File dir, Filter<String> filter ) |
| { |
| // scan directories for a valid project version subdirectory, meaning this must be a project directory |
| File[] files = dir.listFiles( new DirectoryFilter( filter ) ); |
| if ( files != null ) |
| { |
| for ( File file : files ) |
| { |
| if ( isProjectVersion( file ) ) |
| { |
| 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().equals( metadata.getArtifactId() ) ) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private boolean isProjectVersion( File dir ) |
| { |
| final String artifactId = dir.getParentFile().getName(); |
| final String projectVersion = dir.getName(); |
| |
| // check if there is a POM artifact file to ensure it is a version directory |
| File[] files; |
| if ( VersionUtil.isSnapshot( projectVersion ) ) |
| { |
| files = dir.listFiles( new PomFilenameFilter( artifactId, projectVersion ) ); |
| } |
| else |
| { |
| final String pomFile = artifactId + "-" + projectVersion + ".pom"; |
| files = dir.listFiles( new PomFileFilter( pomFile ) ); |
| } |
| if ( files != null && files.length > 0 ) |
| { |
| 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( File directory ) |
| { |
| ArchivaRepositoryMetadata metadata = null; |
| File metadataFile = new File( directory, METADATA_FILENAME ); |
| if ( metadataFile.exists() ) |
| { |
| try |
| { |
| metadata = MavenMetadataReader.read( metadataFile ); |
| } |
| catch ( XMLException e ) |
| { |
| // ignore missing or invalid metadata |
| } |
| } |
| return metadata; |
| } |
| |
| private static class DirectoryFilter |
| implements FilenameFilter |
| { |
| private final Filter<String> filter; |
| |
| public DirectoryFilter( Filter<String> filter ) |
| { |
| this.filter = filter; |
| } |
| |
| @Override |
| public boolean accept( File dir, String name ) |
| { |
| if ( !filter.accept( name ) ) |
| { |
| return false; |
| } |
| else if ( name.startsWith( "." ) ) |
| { |
| return false; |
| } |
| else if ( !new File( dir, name ).isDirectory() ) |
| { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private static class ArtifactDirectoryFilter |
| implements FilenameFilter |
| { |
| private final Filter<String> filter; |
| |
| private ArtifactDirectoryFilter( Filter<String> filter ) |
| { |
| this.filter = filter; |
| } |
| |
| @Override |
| public boolean accept( File dir, String name ) |
| { |
| // TODO compare to logic in maven-repository-layer |
| if ( !filter.accept( name ) ) |
| { |
| return false; |
| } |
| else if ( name.startsWith( "." ) ) |
| { |
| return false; |
| } |
| else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) ) |
| { |
| return false; |
| } |
| else if ( name.equals( METADATA_FILENAME ) ) |
| { |
| return false; |
| } |
| else if ( new File( dir, name ).isDirectory() ) |
| { |
| 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 FilenameFilter |
| { |
| |
| private final String artifactId, projectVersion; |
| |
| private PomFilenameFilter( String artifactId, String projectVersion ) |
| { |
| this.artifactId = artifactId; |
| this.projectVersion = projectVersion; |
| } |
| |
| @Override |
| public boolean accept( File dir, String name ) |
| { |
| 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 FilenameFilter |
| { |
| private final String pomFile; |
| |
| private PomFileFilter( String pomFile ) |
| { |
| this.pomFile = pomFile; |
| } |
| |
| @Override |
| public boolean accept( File dir, String name ) |
| { |
| return pomFile.equals( name ); |
| } |
| } |
| |
| |
| public PathParser getPathParser() |
| { |
| return pathParser; |
| } |
| |
| public void setPathParser( PathParser pathParser ) |
| { |
| this.pathParser = pathParser; |
| } |
| } |