diff options
author | Martin Stockhammer <martin_s@apache.org> | 2019-08-18 20:56:46 +0200 |
---|---|---|
committer | Martin Stockhammer <martin_s@apache.org> | 2019-08-18 20:56:46 +0200 |
commit | 6cf4073a9e73b6517c51dc22db75fd97d2523695 (patch) | |
tree | f969c1eee474fd298092ec8cbf087c5d8f3e0e90 /archiva-modules/metadata/metadata-repository-api/src | |
parent | 118604a706f1fd66bcda450c1ac8370eff60e4cf (diff) | |
download | archiva-6cf4073a9e73b6517c51dc22db75fd97d2523695.tar.gz archiva-6cf4073a9e73b6517c51dc22db75fd97d2523695.zip |
Changing facet factories. Adding stream methods to metadata repository.
Diffstat (limited to 'archiva-modules/metadata/metadata-repository-api/src')
4 files changed, 307 insertions, 28 deletions
diff --git a/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataRepository.java b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataRepository.java index 5048353b5..c35d5f937 100644 --- a/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataRepository.java +++ b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataRepository.java @@ -24,61 +24,187 @@ import org.apache.archiva.metadata.model.MetadataFacet; import org.apache.archiva.metadata.model.ProjectMetadata; import org.apache.archiva.metadata.model.ProjectVersionMetadata; import org.apache.archiva.metadata.model.ProjectVersionReference; +import org.apache.maven.index_shaded.lucene.util.packed.DirectMonotonicReader; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.stream.Stream; +/** + * A Metadata repository provides information about artifact metadata. It does not provide the artifact data itself. + * It may be possible to use the same backend for metadata and storage, but this depends on the backends and they are + * provided by different APIs. + * + * The motivation for this API is to provide fast access to the repository metadata and fulltext search. Also dependencies + * are stored in this repository. + * + * The methods here do not update the artifacts itself. They are only updating the data in the metadata repository. + * That means, if you want to update some artifact, you should make sure to update the artifact itself and the metadata + * repository (either directly or by repository scanning). + * + * Currently we are providing JCR, File based and Cassandra as backend for the metadata. + * + * The metadata repository uses sessions for accessing the data. Please make sure to always close the sessions after using it. + * Best idiom for using the sessions: + * <code> + * try(RepositorySession session = sessionFactory.createSession() { + * // do your stuff + * } + * </code> + * + * It is implementation dependent, if the sessions are really used by the backend. E.g. the file based implementation ignores + * the sessions completely. + * + * Sessions should be closed immediately after usage. If it is expensive to open a session for a given backend. The backend + * should provide a session pool if possible. There are methods for refreshing a session if needed. + * + * You should avoid stacking sessions, that means, do not create a new session in the same thread, when a session is opened already. + * + * Some backend implementations (JCR) update the metadata in the background, that means update of the metadata is not reflected + * immediately. + * + * The base metadata coordinates are: + * <ul> + * <li>Repository ID: The identifier of the repository, where the artifact resides</li> + * <li>Namespace: This is a hierarchical coordinate for locating the projects. E.g. this corresponds to the groupId in maven. </li> + * <li>Project ID: The project itself</li> + * <li>Version: Each project may have different versions.</li> + * <li>Artifact: Artifacts correspond to files / blob data. Each artifact has additional metadata, like name, version, modification time, ...</li> + * </ul> + * + * As the repository connects to some backend either locally or remote, the access to the repository may fail. The methods capsule the + * backend errors into <code>{@link MetadataRepositoryException}</code>. + * + * Facets are the way to provide additional metadata that is not part of the base API. It depends on the repository type (e.g. Maven, NPM, + * not the metadata backend) what facets are stored in addition to the standard metadata. + * Facets have a specific facet ID that represents the schema for the data stored. For creating specific objects for a given + * facet id the <code>{@link org.apache.archiva.metadata.model.MetadataFacetFactory}</code> is used. + * For each facet id there may exist multiple facet instances on each level. Facet instances are identified by their name, which may be + * a hierarchical path. + * The data in each facet instance is stored in properties (key-value pairs). The properties are converted into / from the specific + * facet object. + * + * Facets can be stored on repository, project, version and artifact level. + * + */ public interface MetadataRepository { /** - * Update metadata for a particular project in the metadata repository, or create it if it does not already exist. + * Update metadata for a particular project in the metadata repository, or create it, if it does not already exist. * - * @param session + * @param session The session used for updating. * @param repositoryId the repository the project is in * @param project the project metadata to create or update + * @throws MetadataRepositoryException if the update fails */ void updateProject( RepositorySession session, String repositoryId, ProjectMetadata project ) throws MetadataRepositoryException; + /** + * Update the metadata of a given artifact. If the artifact, namespace, version, project does not exist in the repository it will be created. + * + * @param session The repository session + * @param repositoryId The repository id + * @param namespace The namespace ('.' separated) + * @param projectId The project id + * @param projectVersion The project version + * @param artifactMeta Information about the artifact itself. + * @throws MetadataRepositoryException if something goes wrong during update. + */ void updateArtifact( RepositorySession session, String repositoryId, String namespace, String projectId, String projectVersion, ArtifactMetadata artifactMeta ) throws MetadataRepositoryException; + /** + * Updates the metadata for a specific version of a given project. If the namespace, project, version does not exist, + * it will be created. + * + * @param session The repository session + * @param repositoryId The repository id + * @param namespace The namespace ('.' separated) + * @param projectId The project id + * @param versionMetadata The metadata for the version + * @throws MetadataRepositoryException if something goes wrong during update + */ void updateProjectVersion( RepositorySession session, String repositoryId, String namespace, String projectId, ProjectVersionMetadata versionMetadata ) throws MetadataRepositoryException; /** - * create the namespace in the repository. (if not exist) + * Create the namespace in the repository, if it does not exist. + * Namespaces do not have specific metadata attached. * - * - * @param session - * @param repositoryId - * @param namespace - * @throws MetadataRepositoryException + * @param session The repository session + * @param repositoryId The repository id + * @param namespace The namespace ('.' separated) + * @throws MetadataRepositoryException if something goes wrong during update */ void updateNamespace( RepositorySession session, String repositoryId, String namespace ) throws MetadataRepositoryException; + /** + * Return the facet names stored for the given facet id on the repository level. + * + * @param session The repository session + * @param repositoryId The repository id + * @param facetId The facet id + * @return The list of facet names, or an empty list, if there are no facets stored on this repository for the given facet id. + * @throws MetadataRepositoryException if something goes wrong + */ List<String> getMetadataFacets( RepositorySession session, String repositoryId, String facetId ) throws MetadataRepositoryException; + <T extends MetadataFacet> Stream<T> getMetadataFacetStream( RepositorySession session, String repositoryId, Class<T> facetClazz) + throws MetadataRepositoryException; + + <T extends MetadataFacet> Stream<T> getMetadataFacetStream( RepositorySession session, String repositoryId, Class<T> facetClazz, long offset, long maxEntries) + throws MetadataRepositoryException; + /** + * Returns true, if there is facet data stored for the given id on the repository. The facet data itself + * may be empty. It's just checking if there is data stored for the given facet id. * - * @param session - * @param repositoryId - * @param facetId - * @return true if the repository datas for this facetId - * @throws MetadataRepositoryException + * @param session The repository session + * @param repositoryId The repository id + * @param facetId The facet id + * @return true if there is data stored this facetId on repository level. + * @throws MetadataRepositoryException if something goes wrong * @since 1.4-M4 */ boolean hasMetadataFacet( RepositorySession session, String repositoryId, String facetId ) throws MetadataRepositoryException; + /** + * Returns the facet data stored on the repository level. The facet instance is identified by the facet id and the + * facet name. The returned object is a instance created by using <code>{@link org.apache.archiva.metadata.model.MetadataFacetFactory}</code>. + * + * @param session The repository session + * @param repositoryId The repository id + * @param facetId The facet id + * @param name The attribute name + * @return The facet values + * @throws MetadataRepositoryException if something goes wrong. + */ MetadataFacet getMetadataFacet( RepositorySession session, String repositoryId, String facetId, String name ) throws MetadataRepositoryException; + /** + * Returns the facet instance using the proper class. + * + * @param session The repository session + * @param repositoryId The repository + * @param clazz The facet object class + * @param name The name of the facet + * @param <T> The facet object + * @return The facet instance if it exists. + * @throws MetadataRepositoryException + */ + <T extends MetadataFacet> T getMetadataFacet(RepositorySession session, String repositoryId, Class<T> clazz, String name) + throws MetadataRepositoryException; + void addMetadataFacet( RepositorySession session, String repositoryId, MetadataFacet metadataFacet ) throws MetadataRepositoryException; @@ -102,6 +228,13 @@ public interface MetadataRepository List<ArtifactMetadata> getArtifactsByDateRange( RepositorySession session, String repositoryId, Date startTime, Date endTime ) throws MetadataRepositoryException; + Stream<ArtifactMetadata> getArtifactsByDateRangeStream( RepositorySession session, String repositoryId, ZonedDateTime startTime, ZonedDateTime endTime ) + throws MetadataRepositoryException; + + Stream<ArtifactMetadata> getArtifactsByDateRangeStream( RepositorySession session, String repositoryId, + ZonedDateTime startTime, ZonedDateTime endTime, long offset, long maxEntries ) + throws MetadataRepositoryException; + Collection<ArtifactMetadata> getArtifactsByChecksum( RepositorySession session, String repositoryId, String checksum ) throws MetadataRepositoryException; diff --git a/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataResolver.java b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataResolver.java index 4cd2cfd41..aae2cf079 100644 --- a/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataResolver.java +++ b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataResolver.java @@ -22,11 +22,19 @@ package org.apache.archiva.metadata.repository; import org.apache.archiva.metadata.model.ArtifactMetadata; import org.apache.archiva.metadata.model.ProjectVersionMetadata; import org.apache.archiva.metadata.model.ProjectVersionReference; +import org.apache.archiva.repository.Repository; +import org.apache.archiva.repository.RepositoryType; +import java.util.Arrays; import java.util.Collection; +import java.util.List; public interface MetadataResolver { + default List<RepositoryType> supportsRepositoryTypes() { + return Arrays.asList( RepositoryType.MAVEN ); + } + ProjectVersionMetadata resolveProjectVersion( RepositorySession session, String repoId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException; diff --git a/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataService.java b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataService.java new file mode 100644 index 000000000..21c4f04d9 --- /dev/null +++ b/archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataService.java @@ -0,0 +1,100 @@ +package org.apache.archiva.metadata.repository; + +/* + * 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.metadata.model.MetadataFacet; +import org.apache.archiva.metadata.model.MetadataFacetFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Martin Stockhammer <martin_s@apache.org> + */ + +@SuppressWarnings( "SpringJavaInjectionPointsAutowiringInspection" ) +@Service("metadataService") +public class MetadataService +{ + + private Map<String, MetadataFacetFactory<? extends MetadataFacet>> facetFactories = new HashMap<>( ); + private Map<Class<? extends MetadataFacet>, MetadataFacetFactory<? extends MetadataFacet>> facetFactoriesByClass = new HashMap<>( ); + private Map<String, Class<? extends MetadataFacet>> reverseFactoryMap = new HashMap<>( ); + + private MetadataResolver metadataResolver = null; + + @Inject + ApplicationContext applicationContext; + + + @Inject + public void setMetadataFacetFactories( List<MetadataFacetFactory> factoryList ) { + Map<String, MetadataFacetFactory<? extends MetadataFacet>> facetFactories = new HashMap<>( ); + Map<Class<? extends MetadataFacet>, MetadataFacetFactory<? extends MetadataFacet>> facetFactoriesByClass = new HashMap<>( ); + Map<String, Class<? extends MetadataFacet>> reverseFactoryMap = new HashMap<>( ); + for (MetadataFacetFactory factory : factoryList) { + facetFactories.put( factory.getFacetId( ), factory ); + facetFactoriesByClass.put( factory.getFacetClass( ), factory ); + reverseFactoryMap.put( factory.getFacetId( ), factory.getFacetClass( ) ); + } + this.facetFactories = facetFactories; + this.facetFactoriesByClass = facetFactoriesByClass; + this.reverseFactoryMap = reverseFactoryMap; + } + + public <T extends MetadataFacet> MetadataFacetFactory<T> getFactory(Class<T> facetClazz) { + return (MetadataFacetFactory<T>) facetFactoriesByClass.get( facetClazz ); + } + + public MetadataFacetFactory<?> getFactory(String facetId) { + return facetFactories.get( facetId ); + } + + public Set<String> getSupportedFacets() { + return facetFactories.keySet( ); + } + + public boolean supportsFacet(Class<? extends MetadataFacet> facetClazz) { + return facetFactoriesByClass.containsKey( facetClazz ); + } + + public boolean supportsFacet(String facetId) { + return facetFactories.containsKey( facetId ); + } + + public Class<? extends MetadataFacet> getFactoryClassForId( String facetId ) { + return reverseFactoryMap.get( facetId ); + } + + // Lazy evaluation to avoid problems with circular dependencies during initialization + public MetadataResolver getMetadataResolver() + { + if ( this.metadataResolver == null && applicationContext!=null) + { + this.metadataResolver = applicationContext.getBean( MetadataResolver.class ); + } + return this.metadataResolver; + } +} diff --git a/archiva-modules/metadata/metadata-repository-api/src/test/java/org/apache/archiva/metadata/repository/AbstractMetadataRepositoryTest.java b/archiva-modules/metadata/metadata-repository-api/src/test/java/org/apache/archiva/metadata/repository/AbstractMetadataRepositoryTest.java index a5574d635..ea47a00bf 100644 --- a/archiva-modules/metadata/metadata-repository-api/src/test/java/org/apache/archiva/metadata/repository/AbstractMetadataRepositoryTest.java +++ b/archiva-modules/metadata/metadata-repository-api/src/test/java/org/apache/archiva/metadata/repository/AbstractMetadataRepositoryTest.java @@ -23,6 +23,7 @@ import junit.framework.TestCase; import org.apache.archiva.metadata.generic.GenericMetadataFacet; import org.apache.archiva.metadata.generic.GenericMetadataFacetFactory; import org.apache.archiva.metadata.model.*; +import org.apache.archiva.metadata.model.facets.AbstractMetadataFacetFactory; import org.apache.archiva.repository.Repository; import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner; import org.junit.Before; @@ -35,6 +36,8 @@ import org.springframework.test.context.ContextConfiguration; import java.text.SimpleDateFormat; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -139,46 +142,43 @@ public abstract class AbstractMetadataRepositoryTest } - public static Map<String, MetadataFacetFactory> createTestMetadataFacetFactories( ) + public static List<MetadataFacetFactory> createTestMetadataFacetFactories( ) { - Map<String, MetadataFacetFactory> factories = new HashMap<>( ); - factories.put( TEST_FACET_ID, new MetadataFacetFactory( ) + List<MetadataFacetFactory> factories = new ArrayList<>( ); + factories.add( new MetadataFacetFactory<TestMetadataFacet>( ) { @Override - public MetadataFacet createMetadataFacet( ) + public TestMetadataFacet createMetadataFacet( ) { return new TestMetadataFacet( TEST_METADATA_VALUE ); } @Override - public MetadataFacet createMetadataFacet( String repositoryId, String name ) + public TestMetadataFacet createMetadataFacet( String repositoryId, String name ) { return new TestMetadataFacet( TEST_METADATA_VALUE ); } - } ); - // add to ensure we don't accidentally create an empty facet ID. - factories.put( "", new MetadataFacetFactory( ) - { @Override - public MetadataFacet createMetadataFacet( ) + public Class<TestMetadataFacet> getFacetClass( ) { - return new TestMetadataFacet( "", TEST_VALUE ); + return TestMetadataFacet.class; } @Override - public MetadataFacet createMetadataFacet( String repositoryId, String name ) + public String getFacetId( ) { - return new TestMetadataFacet( "", TEST_VALUE ); + return TEST_FACET_ID; } } ); // for the getArtifactsByProjectVersionMetadata tests - factories.put( GenericMetadataFacet.FACET_ID, new GenericMetadataFacetFactory( ) ); + factories.add( new GenericMetadataFacetFactory( ) ); return factories; } + @Test public void testRootNamespaceWithNoMetadataRepository( ) throws Exception @@ -794,6 +794,22 @@ public abstract class AbstractMetadataRepositoryTest } @Test + public void testGetMetadataFacetByClass( ) + throws Exception + { + try ( RepositorySession session = getSessionFactory( ).createSession( ) ) + { + getRepository( ).addMetadataFacet( session, TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) ); + + TestMetadataFacet test = + (TestMetadataFacet) getRepository( ).getMetadataFacet( session, TEST_REPO_ID, TestMetadataFacet.class, TEST_NAME ); + + assertEquals( new TestMetadataFacet( TEST_VALUE ), test ); + + } + } + + @Test public void testGetMetadataFacetWhenEmpty( ) throws Exception { @@ -858,6 +874,28 @@ public abstract class AbstractMetadataRepositoryTest } @Test + public void testGetMetadataFacetsStream( ) + throws Exception + { + try ( RepositorySession session = getSessionFactory( ).createSession( ) ) + { + getRepository( ).addMetadataFacet( session, TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) ); + } + + try ( RepositorySession session = getSessionFactory( ).createSession( ) ) + { + tryAssert( ( ) -> { + Stream<TestMetadataFacet> str = getRepository( ).getMetadataFacetStream( session, TEST_REPO_ID, TestMetadataFacet.class ); + assertNotNull( str ); + List<TestMetadataFacet> result = str.collect( Collectors.toList( ) ); + assertEquals( 1, result.size( ) ); + assertEquals( TEST_NAME, result.get( 0 ).getName( ) ); + } ); + + } + } + + @Test public void testGetMetadataFacetsWhenEmpty( ) throws Exception { @@ -1617,7 +1655,7 @@ public abstract class AbstractMetadataRepositoryTest assertThat( artifactMetadata.getRepositoryId( ) ).isEqualTo( TEST_REPO_ID ); MetadataFacet facet = artifactMetadata.getFacet( TEST_FACET_ID ); assertThat( facet ).isNotNull( ); - assertThat( facet.toProperties( ) ).isEqualTo( Collections.singletonMap( "foo", TEST_METADATA_VALUE ) ); + assertThat( facet.toProperties( ).get("foo").equals(TEST_METADATA_VALUE) ); } ); } } |