package org.apache.archiva.metadata.repository.file; /* * 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.configuration.ArchivaConfiguration; import org.apache.archiva.configuration.ManagedRepositoryConfiguration; import org.apache.archiva.metadata.model.ArtifactMetadata; import org.apache.archiva.metadata.model.CiManagement; import org.apache.archiva.metadata.model.Dependency; import org.apache.archiva.metadata.model.IssueManagement; import org.apache.archiva.metadata.model.License; import org.apache.archiva.metadata.model.MailingList; import org.apache.archiva.metadata.model.MetadataFacet; import org.apache.archiva.metadata.model.MetadataFacetFactory; import org.apache.archiva.metadata.model.Organization; import org.apache.archiva.metadata.model.ProjectMetadata; import org.apache.archiva.metadata.model.ProjectVersionMetadata; import org.apache.archiva.metadata.model.ProjectVersionReference; import org.apache.archiva.metadata.model.Scm; import org.apache.archiva.metadata.repository.MetadataRepository; import org.apache.archiva.metadata.repository.MetadataRepositoryException; import org.apache.archiva.metadata.repository.MetadataResolutionException; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; public class FileMetadataRepository implements MetadataRepository { private final Map<String, MetadataFacetFactory> metadataFacetFactories; private final ArchivaConfiguration configuration; private Logger log = LoggerFactory.getLogger( FileMetadataRepository.class ); private static final String PROJECT_METADATA_KEY = "project-metadata"; private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata"; private static final String NAMESPACE_METADATA_KEY = "namespace-metadata"; private static final String METADATA_KEY = "metadata"; public FileMetadataRepository( Map<String, MetadataFacetFactory> metadataFacetFactories, ArchivaConfiguration configuration ) { this.metadataFacetFactories = metadataFacetFactories; this.configuration = configuration; } private File getBaseDirectory( String repoId ) throws IOException { // TODO: should be configurable, like the index ManagedRepositoryConfiguration managedRepositoryConfiguration = configuration.getConfiguration().getManagedRepositoriesAsMap().get( repoId ); if ( managedRepositoryConfiguration == null ) { return Files.createTempDirectory( repoId ).toFile(); } String basedir = managedRepositoryConfiguration.getLocation(); return new File( basedir, ".archiva" ); } private File getDirectory( String repoId ) throws IOException { return new File( getBaseDirectory( repoId ), "content" ); } @Override public void updateProject( String repoId, ProjectMetadata project ) { updateProject( repoId, project.getNamespace(), project.getId() ); } private void updateProject( String repoId, String namespace, String id ) { // TODO: this is a more braindead implementation than we would normally expect, for prototyping purposes updateNamespace( repoId, namespace ); try { File namespaceDirectory = new File( getDirectory( repoId ), namespace ); Properties properties = new Properties(); properties.setProperty( "namespace", namespace ); properties.setProperty( "id", id ); writeProperties( properties, new File( namespaceDirectory, id ), PROJECT_METADATA_KEY ); } catch ( IOException e ) { // TODO! log.error( e.getMessage(), e ); } } @Override public void updateProjectVersion( String repoId, String namespace, String projectId, ProjectVersionMetadata versionMetadata ) { try { updateProject( repoId, namespace, projectId ); File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + versionMetadata.getId() ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); // remove properties that are not references or artifacts for ( Object key : new ArrayList( properties.keySet() ) ) { String name = (String) key; if ( !name.contains( ":" ) && !name.equals( "facetIds" ) ) { properties.remove( name ); } // clear the facet contents so old properties are no longer written clearMetadataFacetProperties( versionMetadata.getFacetList(), properties, "" ); } properties.setProperty( "id", versionMetadata.getId() ); setProperty( properties, "name", versionMetadata.getName() ); setProperty( properties, "description", versionMetadata.getDescription() ); setProperty( properties, "url", versionMetadata.getUrl() ); setProperty( properties, "incomplete", String.valueOf( versionMetadata.isIncomplete() ) ); if ( versionMetadata.getScm() != null ) { setProperty( properties, "scm.connection", versionMetadata.getScm().getConnection() ); setProperty( properties, "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() ); setProperty( properties, "scm.url", versionMetadata.getScm().getUrl() ); } if ( versionMetadata.getCiManagement() != null ) { setProperty( properties, "ci.system", versionMetadata.getCiManagement().getSystem() ); setProperty( properties, "ci.url", versionMetadata.getCiManagement().getUrl() ); } if ( versionMetadata.getIssueManagement() != null ) { setProperty( properties, "issue.system", versionMetadata.getIssueManagement().getSystem() ); setProperty( properties, "issue.url", versionMetadata.getIssueManagement().getUrl() ); } if ( versionMetadata.getOrganization() != null ) { setProperty( properties, "org.name", versionMetadata.getOrganization().getName() ); setProperty( properties, "org.url", versionMetadata.getOrganization().getUrl() ); } int i = 0; for ( License license : versionMetadata.getLicenses() ) { setProperty( properties, "license." + i + ".name", license.getName() ); setProperty( properties, "license." + i + ".url", license.getUrl() ); i++; } i = 0; for ( MailingList mailingList : versionMetadata.getMailingLists() ) { setProperty( properties, "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() ); setProperty( properties, "mailingList." + i + ".name", mailingList.getName() ); setProperty( properties, "mailingList." + i + ".post", mailingList.getPostAddress() ); setProperty( properties, "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() ); setProperty( properties, "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() ); setProperty( properties, "mailingList." + i + ".otherArchives", join( mailingList.getOtherArchives() ) ); i++; } i = 0; ProjectVersionReference reference = new ProjectVersionReference(); reference.setNamespace( namespace ); reference.setProjectId( projectId ); reference.setProjectVersion( versionMetadata.getId() ); reference.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY ); for ( Dependency dependency : versionMetadata.getDependencies() ) { setProperty( properties, "dependency." + i + ".classifier", dependency.getClassifier() ); setProperty( properties, "dependency." + i + ".scope", dependency.getScope() ); setProperty( properties, "dependency." + i + ".systemPath", dependency.getSystemPath() ); setProperty( properties, "dependency." + i + ".artifactId", dependency.getArtifactId() ); setProperty( properties, "dependency." + i + ".groupId", dependency.getGroupId() ); setProperty( properties, "dependency." + i + ".version", dependency.getVersion() ); setProperty( properties, "dependency." + i + ".type", dependency.getType() ); setProperty( properties, "dependency." + i + ".optional", String.valueOf( dependency.isOptional() ) ); updateProjectReference( repoId, dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), reference ); i++; } Set<String> facetIds = new LinkedHashSet<String>( versionMetadata.getFacetIds() ); facetIds.addAll( Arrays.asList( properties.getProperty( "facetIds", "" ).split( "," ) ) ); properties.setProperty( "facetIds", join( facetIds ) ); updateProjectVersionFacets( versionMetadata, properties ); writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY ); } catch ( IOException e ) { // TODO log.error( e.getMessage(), e ); } } private void updateProjectVersionFacets( ProjectVersionMetadata versionMetadata, Properties properties ) { for ( MetadataFacet facet : versionMetadata.getFacetList() ) { for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() ) { properties.setProperty( facet.getFacetId() + ":" + entry.getKey(), entry.getValue() ); } } } private static void clearMetadataFacetProperties( Collection<MetadataFacet> facetList, Properties properties, String prefix ) { List<Object> propsToRemove = new ArrayList<>(); for ( MetadataFacet facet : facetList ) { for ( Object key : new ArrayList( properties.keySet() ) ) { String keyString = (String) key; if ( keyString.startsWith( prefix + facet.getFacetId() + ":" ) ) { propsToRemove.add( key ); } } } for ( Object key : propsToRemove ) { properties.remove( key ); } } private void updateProjectReference( String repoId, String namespace, String projectId, String projectVersion, ProjectVersionReference reference ) { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); int i = Integer.parseInt( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1; setProperty( properties, "ref:lastReferenceNum", Integer.toString( i ) ); setProperty( properties, "ref:reference." + i + ".namespace", reference.getNamespace() ); setProperty( properties, "ref:reference." + i + ".projectId", reference.getProjectId() ); setProperty( properties, "ref:reference." + i + ".projectVersion", reference.getProjectVersion() ); setProperty( properties, "ref:reference." + i + ".referenceType", reference.getReferenceType().toString() ); writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY ); } catch ( IOException e ) { // TODO log.error( e.getMessage(), e ); } } @Override public void updateNamespace( String repoId, String namespace ) { try { File namespaceDirectory = new File( getDirectory( repoId ), namespace ); Properties properties = new Properties(); properties.setProperty( "namespace", namespace ); writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY ); } catch ( IOException e ) { // TODO! log.error( e.getMessage(), e ); } } @Override public List<String> getMetadataFacets( String repoId, String facetId ) throws MetadataRepositoryException { try { File directory = getMetadataDirectory( repoId, facetId ); List<String> facets = new ArrayList<>(); recurse( facets, "", directory ); return facets; } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public boolean hasMetadataFacet( String repositoryId, String facetId ) throws MetadataRepositoryException { // TODO could be improved a bit return !getMetadataFacets( repositoryId, facetId ).isEmpty(); } private void recurse( List<String> facets, String prefix, File directory ) { File[] list = directory.listFiles(); if ( list != null ) { for ( File dir : list ) { if ( dir.isDirectory() ) { recurse( facets, prefix + "/" + dir.getName(), dir ); } else if ( dir.getName().equals( METADATA_KEY + ".properties" ) ) { facets.add( prefix.substring( 1 ) ); } } } } @Override public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name ) { Properties properties; try { properties = readProperties( new File( getMetadataDirectory( repositoryId, facetId ), name ), METADATA_KEY ); } catch ( FileNotFoundException e ) { return null; } catch ( IOException e ) { // TODO log.error( e.getMessage(), e ); return null; } MetadataFacet metadataFacet = null; MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId ); if ( metadataFacetFactory != null ) { metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name ); Map<String, String> map = new HashMap<>(); for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; map.put( property, properties.getProperty( property ) ); } metadataFacet.fromProperties( map ); } return metadataFacet; } @Override public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet ) { Properties properties = new Properties(); properties.putAll( metadataFacet.toProperties() ); try { File directory = new File( getMetadataDirectory( repositoryId, metadataFacet.getFacetId() ), metadataFacet.getName() ); writeProperties( properties, directory, METADATA_KEY ); } catch ( IOException e ) { // TODO! log.error( e.getMessage(), e ); } } @Override public void removeMetadataFacets( String repositoryId, String facetId ) throws MetadataRepositoryException { try { File dir = getMetadataDirectory( repositoryId, facetId ); FileUtils.deleteDirectory( dir ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeMetadataFacet( String repoId, String facetId, String name ) throws MetadataRepositoryException { try { File dir = new File( getMetadataDirectory( repoId, facetId ), name ); FileUtils.deleteDirectory( dir ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime ) throws MetadataRepositoryException { try { // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index // of this information (eg. in Lucene, as before) List<ArtifactMetadata> artifacts = new ArrayList<>(); for ( String ns : getRootNamespaces( repoId ) ) { getArtifactsByDateRange( artifacts, repoId, ns, startTime, endTime ); } Collections.sort( artifacts, new ArtifactComparator() ); return artifacts; } catch ( MetadataResolutionException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } private void getArtifactsByDateRange( List<ArtifactMetadata> artifacts, String repoId, String ns, Date startTime, Date endTime ) throws MetadataRepositoryException { try { for ( String namespace : getNamespaces( repoId, ns ) ) { getArtifactsByDateRange( artifacts, repoId, ns + "." + namespace, startTime, endTime ); } for ( String project : getProjects( repoId, ns ) ) { for ( String version : getProjectVersions( repoId, ns, project ) ) { for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) ) { if ( startTime == null || startTime.before( artifact.getWhenGathered() ) ) { if ( endTime == null || endTime.after( artifact.getWhenGathered() ) ) { artifacts.add( artifact ); } } } } } } catch ( MetadataResolutionException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { try { Map<String, ArtifactMetadata> artifacts = new HashMap<>(); File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); for ( Map.Entry entry : properties.entrySet() ) { String name = (String) entry.getKey(); StringTokenizer tok = new StringTokenizer( name, ":" ); if ( tok.hasMoreTokens() && "artifact".equals( tok.nextToken() ) ) { String field = tok.nextToken(); String id = tok.nextToken(); ArtifactMetadata artifact = artifacts.get( id ); if ( artifact == null ) { artifact = new ArtifactMetadata(); artifact.setRepositoryId( repoId ); artifact.setNamespace( namespace ); artifact.setProject( projectId ); artifact.setProjectVersion( projectVersion ); artifact.setVersion( projectVersion ); artifact.setId( id ); artifacts.put( id, artifact ); } String value = (String) entry.getValue(); if ( "updated".equals( field ) ) { artifact.setFileLastModified( Long.parseLong( value ) ); } else if ( "size".equals( field ) ) { artifact.setSize( Long.valueOf( value ) ); } else if ( "whenGathered".equals( field ) ) { artifact.setWhenGathered( new Date( Long.parseLong( value ) ) ); } else if ( "version".equals( field ) ) { artifact.setVersion( value ); } else if ( "md5".equals( field ) ) { artifact.setMd5( value ); } else if ( "sha1".equals( field ) ) { artifact.setSha1( value ); } else if ( "facetIds".equals( field ) ) { if ( value.length() > 0 ) { String propertyPrefix = "artifact:facet:" + id + ":"; for ( String facetId : value.split( "," ) ) { MetadataFacetFactory factory = metadataFacetFactories.get( facetId ); if ( factory == null ) { log.error( "Attempted to load unknown artifact metadata facet: " + facetId ); } else { MetadataFacet facet = factory.createMetadataFacet(); String prefix = propertyPrefix + facet.getFacetId(); Map<String, String> map = new HashMap<>(); for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; if ( property.startsWith( prefix ) ) { map.put( property.substring( prefix.length() + 1 ), properties.getProperty( property ) ); } } facet.fromProperties( map ); artifact.addFacet( facet ); } } } updateArtifactFacets( artifact, properties ); } } } return artifacts.values(); } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public void save() { // it's all instantly persisted } @Override public void close() { // nothing additional to close } @Override public void revert() { log.warn( "Attempted to revert a session, but the file-based repository storage doesn't support it" ); } @Override public boolean canObtainAccess( Class<?> aClass ) { return false; } @Override public <T> T obtainAccess( Class<T> aClass ) { throw new IllegalArgumentException( "Access using " + aClass + " is not supported on the file metadata storage" ); } private void updateArtifactFacets( ArtifactMetadata artifact, Properties properties ) { String propertyPrefix = "artifact:facet:" + artifact.getId() + ":"; for ( MetadataFacet facet : artifact.getFacetList() ) { for ( Map.Entry<String, String> e : facet.toProperties().entrySet() ) { String key = propertyPrefix + facet.getFacetId() + ":" + e.getKey(); properties.setProperty( key, e.getValue() ); } } } @Override public Collection<String> getRepositories() { List<String> repositories = new ArrayList<>(); for ( ManagedRepositoryConfiguration managedRepositoryConfiguration : configuration.getConfiguration().getManagedRepositories() ) { repositories.add( managedRepositoryConfiguration.getId() ); } return repositories; } @Override public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum ) throws MetadataRepositoryException { try { // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index // of this information (eg. in Lucene, as before) // alternatively, we could build a referential tree in the content repository, however it would need some levels // of depth to avoid being too broad to be useful (eg. /repository/checksums/a/ab/abcdef1234567) List<ArtifactMetadata> artifacts = new ArrayList<>(); for ( String ns : getRootNamespaces( repositoryId ) ) { getArtifactsByChecksum( artifacts, repositoryId, ns, checksum ); } return artifacts; } catch ( MetadataResolutionException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeNamespace( String repositoryId, String project ) throws MetadataRepositoryException { try { File namespaceDirectory = new File( getDirectory( repositoryId ), project ); FileUtils.deleteDirectory( namespaceDirectory ); //Properties properties = new Properties(); //properties.setProperty( "namespace", namespace ); //writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeArtifact( ArtifactMetadata artifactMetadata, String baseVersion ) throws MetadataRepositoryException { try { File directory = new File( getDirectory( artifactMetadata.getRepositoryId() ), artifactMetadata.getNamespace() + "/" + artifactMetadata.getProject() + "/" + baseVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); String id = artifactMetadata.getId(); properties.remove( "artifact:updated:" + id ); properties.remove( "artifact:whenGathered:" + id ); properties.remove( "artifact:size:" + id ); properties.remove( "artifact:md5:" + id ); properties.remove( "artifact:sha1:" + id ); properties.remove( "artifact:version:" + id ); properties.remove( "artifact:facetIds:" + id ); String prefix = "artifact:facet:" + id + ":"; for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; if ( property.startsWith( prefix ) ) { properties.remove( property ); } } writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeArtifact( String repoId, String namespace, String project, String version, String id ) throws MetadataRepositoryException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + project + "/" + version ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); properties.remove( "artifact:updated:" + id ); properties.remove( "artifact:whenGathered:" + id ); properties.remove( "artifact:size:" + id ); properties.remove( "artifact:md5:" + id ); properties.remove( "artifact:sha1:" + id ); properties.remove( "artifact:version:" + id ); properties.remove( "artifact:facetIds:" + id ); String prefix = "artifact:facet:" + id + ":"; for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; if ( property.startsWith( prefix ) ) { properties.remove( property ); } } FileUtils.deleteDirectory( directory ); //writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } /** * FIXME implements this !!!! * * @param repositoryId * @param namespace * @param project * @param projectVersion * @param metadataFacet will remove artifacts which have this {@link MetadataFacet} using equals * @throws MetadataRepositoryException */ @Override public void removeArtifact( String repositoryId, String namespace, String project, String projectVersion, MetadataFacet metadataFacet ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "not implemented" ); } @Override public void removeRepository( String repoId ) throws MetadataRepositoryException { try { File dir = getDirectory( repoId ); FileUtils.deleteDirectory( dir ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } private void getArtifactsByChecksum( List<ArtifactMetadata> artifacts, String repositoryId, String ns, String checksum ) throws MetadataRepositoryException { try { for ( String namespace : getNamespaces( repositoryId, ns ) ) { getArtifactsByChecksum( artifacts, repositoryId, ns + "." + namespace, checksum ); } for ( String project : getProjects( repositoryId, ns ) ) { for ( String version : getProjectVersions( repositoryId, ns, project ) ) { for ( ArtifactMetadata artifact : getArtifacts( repositoryId, ns, project, version ) ) { if ( checksum.equals( artifact.getMd5() ) || checksum.equals( artifact.getSha1() ) ) { artifacts.add( artifact ); } } } } } catch ( MetadataResolutionException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "not yet implemented in File backend" ); } @Override public List<ArtifactMetadata> getArtifactsByMetadata( String key, String value, String repositoryId ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "not yet implemented in File backend" ); } @Override public List<ArtifactMetadata> getArtifactsByProperty( String key, String value, String repositoryId ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "getArtifactsByProperty not yet implemented in File backend" ); } private File getMetadataDirectory( String repoId, String facetId ) throws IOException { return new File( getBaseDirectory( repoId ), "facets/" + facetId ); } private String join( Collection<String> ids ) { if ( ids != null && !ids.isEmpty() ) { StringBuilder s = new StringBuilder(); for ( String id : ids ) { s.append( id ); s.append( "," ); } return s.substring( 0, s.length() - 1 ); } return ""; } private void setProperty( Properties properties, String name, String value ) { if ( value != null ) { properties.setProperty( name, value ); } } @Override public void updateArtifact( String repoId, String namespace, String projectId, String projectVersion, ArtifactMetadata artifact ) { try { ProjectVersionMetadata metadata = new ProjectVersionMetadata(); metadata.setId( projectVersion ); updateProjectVersion( repoId, namespace, projectId, metadata ); File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); clearMetadataFacetProperties( artifact.getFacetList(), properties, "artifact:facet:" + artifact.getId() + ":" ); String id = artifact.getId(); properties.setProperty( "artifact:updated:" + id, Long.toString( artifact.getFileLastModified().getTime() ) ); properties.setProperty( "artifact:whenGathered:" + id, Long.toString( artifact.getWhenGathered().getTime() ) ); properties.setProperty( "artifact:size:" + id, Long.toString( artifact.getSize() ) ); if ( artifact.getMd5() != null ) { properties.setProperty( "artifact:md5:" + id, artifact.getMd5() ); } if ( artifact.getSha1() != null ) { properties.setProperty( "artifact:sha1:" + id, artifact.getSha1() ); } properties.setProperty( "artifact:version:" + id, artifact.getVersion() ); Set<String> facetIds = new LinkedHashSet<String>( artifact.getFacetIds() ); String property = "artifact:facetIds:" + id; facetIds.addAll( Arrays.asList( properties.getProperty( property, "" ).split( "," ) ) ); properties.setProperty( property, join( facetIds ) ); updateArtifactFacets( artifact, properties ); writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY ); } catch ( IOException e ) { // TODO log.error( e.getMessage(), e ); } } private Properties readOrCreateProperties( File directory, String propertiesKey ) { try { return readProperties( directory, propertiesKey ); } catch ( FileNotFoundException | NoSuchFileException e ) { // ignore and return new properties } catch ( IOException e ) { // TODO log.error( e.getMessage(), e ); } return new Properties(); } private Properties readProperties( File directory, String propertiesKey ) throws IOException { Properties properties = new Properties(); try (InputStream in = Files.newInputStream( new File( directory, propertiesKey + ".properties" ).toPath() )) { properties.load( in ); } return properties; } @Override public ProjectMetadata getProject( String repoId, String namespace, String projectId ) throws MetadataResolutionException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId ); Properties properties = readOrCreateProperties( directory, PROJECT_METADATA_KEY ); ProjectMetadata project = null; String id = properties.getProperty( "id" ); if ( id != null ) { project = new ProjectMetadata(); project.setNamespace( properties.getProperty( "namespace" ) ); project.setId( id ); } return project; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public ProjectVersionMetadata getProjectVersion( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); String id = properties.getProperty( "id" ); ProjectVersionMetadata versionMetadata = null; if ( id != null ) { versionMetadata = new ProjectVersionMetadata(); versionMetadata.setId( id ); versionMetadata.setName( properties.getProperty( "name" ) ); versionMetadata.setDescription( properties.getProperty( "description" ) ); versionMetadata.setUrl( properties.getProperty( "url" ) ); versionMetadata.setIncomplete( Boolean.valueOf( properties.getProperty( "incomplete", "false" ) ) ); String scmConnection = properties.getProperty( "scm.connection" ); String scmDeveloperConnection = properties.getProperty( "scm.developerConnection" ); String scmUrl = properties.getProperty( "scm.url" ); if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null ) { Scm scm = new Scm(); scm.setConnection( scmConnection ); scm.setDeveloperConnection( scmDeveloperConnection ); scm.setUrl( scmUrl ); versionMetadata.setScm( scm ); } String ciSystem = properties.getProperty( "ci.system" ); String ciUrl = properties.getProperty( "ci.url" ); if ( ciSystem != null || ciUrl != null ) { CiManagement ci = new CiManagement(); ci.setSystem( ciSystem ); ci.setUrl( ciUrl ); versionMetadata.setCiManagement( ci ); } String issueSystem = properties.getProperty( "issue.system" ); String issueUrl = properties.getProperty( "issue.url" ); if ( issueSystem != null || issueUrl != null ) { IssueManagement issueManagement = new IssueManagement(); issueManagement.setSystem( issueSystem ); issueManagement.setUrl( issueUrl ); versionMetadata.setIssueManagement( issueManagement ); } String orgName = properties.getProperty( "org.name" ); String orgUrl = properties.getProperty( "org.url" ); if ( orgName != null || orgUrl != null ) { Organization org = new Organization(); org.setName( orgName ); org.setUrl( orgUrl ); versionMetadata.setOrganization( org ); } boolean done = false; int i = 0; while ( !done ) { String licenseName = properties.getProperty( "license." + i + ".name" ); String licenseUrl = properties.getProperty( "license." + i + ".url" ); if ( licenseName != null || licenseUrl != null ) { License license = new License(); license.setName( licenseName ); license.setUrl( licenseUrl ); versionMetadata.addLicense( license ); } else { done = true; } i++; } done = false; i = 0; while ( !done ) { String mailingListName = properties.getProperty( "mailingList." + i + ".name" ); if ( mailingListName != null ) { MailingList mailingList = new MailingList(); mailingList.setName( mailingListName ); mailingList.setMainArchiveUrl( properties.getProperty( "mailingList." + i + ".archive" ) ); String p = properties.getProperty( "mailingList." + i + ".otherArchives" ); if ( p != null && p.length() > 0 ) { mailingList.setOtherArchives( Arrays.asList( p.split( "," ) ) ); } else { mailingList.setOtherArchives( Collections.<String>emptyList() ); } mailingList.setPostAddress( properties.getProperty( "mailingList." + i + ".post" ) ); mailingList.setSubscribeAddress( properties.getProperty( "mailingList." + i + ".subscribe" ) ); mailingList.setUnsubscribeAddress( properties.getProperty( "mailingList." + i + ".unsubscribe" ) ); versionMetadata.addMailingList( mailingList ); } else { done = true; } i++; } done = false; i = 0; while ( !done ) { String dependencyArtifactId = properties.getProperty( "dependency." + i + ".artifactId" ); if ( dependencyArtifactId != null ) { Dependency dependency = new Dependency(); dependency.setArtifactId( dependencyArtifactId ); dependency.setGroupId( properties.getProperty( "dependency." + i + ".groupId" ) ); dependency.setClassifier( properties.getProperty( "dependency." + i + ".classifier" ) ); dependency.setOptional( Boolean.valueOf( properties.getProperty( "dependency." + i + ".optional" ) ) ); dependency.setScope( properties.getProperty( "dependency." + i + ".scope" ) ); dependency.setSystemPath( properties.getProperty( "dependency." + i + ".systemPath" ) ); dependency.setType( properties.getProperty( "dependency." + i + ".type" ) ); dependency.setVersion( properties.getProperty( "dependency." + i + ".version" ) ); dependency.setOptional( Boolean.valueOf( properties.getProperty( "dependency." + i + ".optional" ) ) ); versionMetadata.addDependency( dependency ); } else { done = true; } i++; } String facetIds = properties.getProperty( "facetIds", "" ); if ( facetIds.length() > 0 ) { for ( String facetId : facetIds.split( "," ) ) { MetadataFacetFactory factory = metadataFacetFactories.get( facetId ); if ( factory == null ) { log.error( "Attempted to load unknown project version metadata facet: {}", facetId ); } else { MetadataFacet facet = factory.createMetadataFacet(); Map<String, String> map = new HashMap<>(); for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; if ( property.startsWith( facet.getFacetId() ) ) { map.put( property.substring( facet.getFacetId().length() + 1 ), properties.getProperty( property ) ); } } facet.fromProperties( map ); versionMetadata.addFacet( facet ); } } } updateProjectVersionFacets( versionMetadata, properties ); } return versionMetadata; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public Collection<String> getArtifactVersions( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); Set<String> versions = new HashSet<String>(); for ( Map.Entry entry : properties.entrySet() ) { String name = (String) entry.getKey(); if ( name.startsWith( "artifact:version:" ) ) { versions.add( (String) entry.getValue() ); } } return versions; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); int numberOfRefs = Integer.parseInt( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1; List<ProjectVersionReference> references = new ArrayList<>(); for ( int i = 0; i < numberOfRefs; i++ ) { ProjectVersionReference reference = new ProjectVersionReference(); reference.setProjectId( properties.getProperty( "ref:reference." + i + ".projectId" ) ); reference.setNamespace( properties.getProperty( "ref:reference." + i + ".namespace" ) ); reference.setProjectVersion( properties.getProperty( "ref:reference." + i + ".projectVersion" ) ); reference.setReferenceType( ProjectVersionReference.ReferenceType.valueOf( properties.getProperty( "ref:reference." + i + ".referenceType" ) ) ); references.add( reference ); } return references; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public Collection<String> getRootNamespaces( String repoId ) throws MetadataResolutionException { return getNamespaces( repoId, null ); } @Override public Collection<String> getNamespaces( String repoId, String baseNamespace ) throws MetadataResolutionException { try { List<String> allNamespaces = new ArrayList<>(); File directory = getDirectory( repoId ); File[] files = directory.listFiles(); if ( files != null ) { for ( File namespace : files ) { if ( new File( namespace, NAMESPACE_METADATA_KEY + ".properties" ).exists() ) { allNamespaces.add( namespace.getName() ); } } } Set<String> namespaces = new LinkedHashSet<>(); int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0; for ( String namespace : allNamespaces ) { if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) ) { int i = namespace.indexOf( '.', fromIndex ); if ( i >= 0 ) { namespaces.add( namespace.substring( fromIndex, i ) ); } else { namespaces.add( namespace.substring( fromIndex ) ); } } } return new ArrayList<>( namespaces ); } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public Collection<String> getProjects( String repoId, String namespace ) throws MetadataResolutionException { try { List<String> projects = new ArrayList<>(); File directory = new File( getDirectory( repoId ), namespace ); File[] files = directory.listFiles(); if ( files != null ) { for ( File project : files ) { if ( new File( project, PROJECT_METADATA_KEY + ".properties" ).exists() ) { projects.add( project.getName() ); } } } return projects; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public Collection<String> getProjectVersions( String repoId, String namespace, String projectId ) throws MetadataResolutionException { try { List<String> projectVersions = new ArrayList<>(); File directory = new File( getDirectory( repoId ), namespace + "/" + projectId ); File[] files = directory.listFiles(); if ( files != null ) { for ( File projectVersion : files ) { if ( new File( projectVersion, PROJECT_VERSION_METADATA_KEY + ".properties" ).exists() ) { projectVersions.add( projectVersion.getName() ); } } } return projectVersions; } catch ( IOException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } } @Override public void removeProject( String repositoryId, String namespace, String projectId ) throws MetadataRepositoryException { try { File directory = new File( getDirectory( repositoryId ), namespace + "/" + projectId ); FileUtils.deleteDirectory( directory ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeProjectVersion( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataRepositoryException { try { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId + "/" + projectVersion ); FileUtils.deleteDirectory( directory ); } catch ( IOException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } private void writeProperties( Properties properties, File directory, String propertiesKey ) throws IOException { directory.mkdirs(); try (OutputStream os = Files.newOutputStream( new File( directory, propertiesKey + ".properties" ).toPath() )) { properties.store( os, null ); } } private static class ArtifactComparator implements Comparator<ArtifactMetadata> { @Override public int compare( ArtifactMetadata artifact1, ArtifactMetadata artifact2 ) { if ( artifact1.getWhenGathered() == artifact2.getWhenGathered() ) { return 0; } if ( artifact1.getWhenGathered() == null ) { return 1; } if ( artifact2.getWhenGathered() == null ) { return -1; } return artifact1.getWhenGathered().compareTo( artifact2.getWhenGathered() ); } } @Override public List<ArtifactMetadata> getArtifacts( String repoId ) throws MetadataRepositoryException { try { List<ArtifactMetadata> artifacts = new ArrayList<>(); for ( String ns : getRootNamespaces( repoId ) ) { getArtifacts( artifacts, repoId, ns ); } return artifacts; } catch ( MetadataResolutionException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } private void getArtifacts( List<ArtifactMetadata> artifacts, String repoId, String ns ) throws MetadataResolutionException { for ( String namespace : getNamespaces( repoId, ns ) ) { getArtifacts( artifacts, repoId, ns + "." + namespace ); } for ( String project : getProjects( repoId, ns ) ) { for ( String version : getProjectVersions( repoId, ns, project ) ) { for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) ) { artifacts.add( artifact ); } } } } @Override public List<ArtifactMetadata> searchArtifacts( String text, String repositoryId, boolean exact ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "searchArtifacts not yet implemented in File backend" ); } @Override public List<ArtifactMetadata> searchArtifacts( String key, String text, String repositoryId, boolean exact ) throws MetadataRepositoryException { throw new UnsupportedOperationException( "searchArtifacts not yet implemented in File backend" ); } }