From 4423d46b4f7f8f747513052d5d0a7a01eb9440c7 Mon Sep 17 00:00:00 2001 From: Brett Porter Date: Fri, 17 Dec 2010 06:26:09 +0000 Subject: [PATCH] [MRM-1327] very rough prototype of a JCR based storage mechanism. Passes tests, but needs to be cleaned up and properly integrated, then tested for performance git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1050283 13f79535-47bb-0310-9956-ffa450edef68 --- .../AbstractMetadataRepositoryTest.java | 300 +++- .../file/FileMetadataRepository.java | 37 +- .../plugins/metadata-store-jcr/pom.xml | 80 ++ .../repository/jcr/JcrMetadataRepository.java | 1245 +++++++++++++++++ .../jcr/JcrMetadataRepositoryTest.java | 58 + .../src/test/repository.xml | 49 + archiva-modules/plugins/pom.xml | 1 + pom.xml | 8 +- 8 files changed, 1721 insertions(+), 57 deletions(-) create mode 100644 archiva-modules/plugins/metadata-store-jcr/pom.xml create mode 100644 archiva-modules/plugins/metadata-store-jcr/src/main/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepository.java create mode 100644 archiva-modules/plugins/metadata-store-jcr/src/test/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepositoryTest.java create mode 100644 archiva-modules/plugins/metadata-store-jcr/src/test/repository.xml 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 fd8362f48..a099bfa60 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 @@ -20,11 +20,18 @@ package org.apache.archiva.metadata.repository; */ 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.codehaus.plexus.spring.PlexusInSpringTestCase; import java.util.ArrayList; @@ -102,7 +109,87 @@ public abstract class AbstractMetadataRepositoryTest assertEquals( Collections.emptyList(), namespaces ); } + public void testGetNamespaceOnly() + { + assertEquals( Collections.emptyList(), repository.getRootNamespaces( TEST_REPO_ID ) ); + + repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); + + assertEquals( Collections.singletonList( TEST_NAMESPACE ), repository.getRootNamespaces( TEST_REPO_ID ) ); + } + + public void testGetProjectOnly() + { + assertNull( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) ); + assertEquals( Collections.emptyList(), repository.getRootNamespaces( TEST_REPO_ID ) ); + + ProjectMetadata project = new ProjectMetadata(); + project.setId( TEST_PROJECT ); + project.setNamespace( TEST_NAMESPACE ); + + repository.updateProject( TEST_REPO_ID, project ); + + project = repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ); + assertEquals( TEST_PROJECT, project.getId() ); + assertEquals( TEST_NAMESPACE, project.getNamespace() ); + + // test that namespace is also constructed + assertEquals( Collections.singletonList( TEST_NAMESPACE ), repository.getRootNamespaces( TEST_REPO_ID ) ); + } + + public void testGetProjectVersionOnly() + throws MetadataResolutionException + { + assertNull( repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ) ); + assertNull( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) ); + assertEquals( Collections.emptyList(), repository.getRootNamespaces( TEST_REPO_ID ) ); + + ProjectVersionMetadata metadata = new ProjectVersionMetadata(); + metadata.setId( TEST_PROJECT_VERSION ); + + repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata ); + + metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); + assertEquals( TEST_PROJECT_VERSION, metadata.getId() ); + + // test that namespace and project is also constructed + assertEquals( Collections.singletonList( TEST_NAMESPACE ), repository.getRootNamespaces( TEST_REPO_ID ) ); + ProjectMetadata projectMetadata = repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ); + assertEquals( TEST_PROJECT, projectMetadata.getId() ); + assertEquals( TEST_NAMESPACE, projectMetadata.getNamespace() ); + } + + public void testGetArtifactOnly() + throws MetadataResolutionException + { + assertEquals( Collections.emptyList(), + new ArrayList( + repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ) ) ); + assertNull( repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ) ); + assertNull( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) ); + assertEquals( Collections.emptyList(), repository.getRootNamespaces( TEST_REPO_ID ) ); + + ArtifactMetadata metadata = createArtifact(); + + repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata ); + + Collection artifacts = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, + TEST_PROJECT_VERSION ); + assertEquals( Collections.singletonList( metadata ), new ArrayList( artifacts ) ); + + // test that namespace, project and project version is also constructed + assertEquals( Collections.singletonList( TEST_NAMESPACE ), repository.getRootNamespaces( TEST_REPO_ID ) ); + + ProjectMetadata projectMetadata = repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ); + assertEquals( TEST_PROJECT, projectMetadata.getId() ); + assertEquals( TEST_NAMESPACE, projectMetadata.getNamespace() ); + + ProjectVersionMetadata projectVersionMetadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); + assertEquals( TEST_PROJECT_VERSION, projectVersionMetadata.getId() ); + } + public void testUpdateProjectVersionMetadataWithNoOtherArchives() + throws MetadataResolutionException { ProjectVersionMetadata metadata = new ProjectVersionMetadata(); metadata.setId( TEST_PROJECT_VERSION ); @@ -111,6 +198,134 @@ public abstract class AbstractMetadataRepositoryTest mailingList.setOtherArchives( Collections.emptyList() ); metadata.setMailingLists( Collections.singletonList( mailingList ) ); repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata ); + + metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); + assertEquals( TEST_PROJECT_VERSION, metadata.getId() ); + assertEquals( 1, metadata.getMailingLists().size() ); + mailingList = metadata.getMailingLists().get( 0 ); + assertEquals( "Foo List", mailingList.getName() ); + assertEquals( Collections.emptyList(), mailingList.getOtherArchives() ); + } + + public void testUpdateProjectVersionMetadataWithAllElements() + throws MetadataResolutionException + { + ProjectVersionMetadata metadata = new ProjectVersionMetadata(); + metadata.setId( TEST_PROJECT_VERSION ); + + metadata.setName( "project name" ); + metadata.setDescription( "project description" ); + + MailingList mailingList = new MailingList(); + mailingList.setName( "Foo List" ); + mailingList.setOtherArchives( Collections.singletonList( "other archive" ) ); + metadata.setMailingLists( Collections.singletonList( mailingList ) ); + + Scm scm = new Scm(); + scm.setConnection( "connection" ); + scm.setDeveloperConnection( "dev conn" ); + scm.setUrl( "url" ); + metadata.setScm( scm ); + + CiManagement ci = new CiManagement(); + ci.setSystem( "system" ); + ci.setUrl( "ci url" ); + metadata.setCiManagement( ci ); + + IssueManagement tracker = new IssueManagement(); + tracker.setSystem( "system" ); + tracker.setUrl( "issue tracker url" ); + metadata.setIssueManagement( tracker ); + + Organization org = new Organization(); + org.setName( "org name" ); + org.setUrl( "url" ); + metadata.setOrganization( org ); + + License l = new License(); + l.setName( "license name" ); + l.setUrl( "license url" ); + metadata.addLicense( l ); + + Dependency d = new Dependency(); + d.setArtifactId( "artifactId" ); + d.setClassifier( "classifier" ); + d.setGroupId( "groupId" ); + d.setScope( "scope" ); + d.setSystemPath( "system path" ); + d.setType( "type" ); + d.setVersion( "version" ); + metadata.addDependency( d ); + + repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata ); + + metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); + assertEquals( TEST_PROJECT_VERSION, metadata.getId() ); + assertEquals( TEST_PROJECT_VERSION, metadata.getVersion() ); + assertEquals( "project name", metadata.getName() ); + assertEquals( "project description", metadata.getDescription() ); + + assertEquals( 1, metadata.getMailingLists().size() ); + mailingList = metadata.getMailingLists().get( 0 ); + assertEquals( "Foo List", mailingList.getName() ); + assertEquals( Collections.singletonList( "other archive" ), mailingList.getOtherArchives() ); + + assertEquals( "connection", metadata.getScm().getConnection() ); + assertEquals( "dev conn", metadata.getScm().getDeveloperConnection() ); + assertEquals( "url", metadata.getScm().getUrl() ); + + assertEquals( "system", metadata.getCiManagement().getSystem() ); + assertEquals( "ci url", metadata.getCiManagement().getUrl() ); + + assertEquals( "system", metadata.getIssueManagement().getSystem() ); + assertEquals( "issue tracker url", metadata.getIssueManagement().getUrl() ); + + assertEquals( "org name", metadata.getOrganization().getName() ); + assertEquals( "url", metadata.getOrganization().getUrl() ); + + assertEquals( 1, metadata.getLicenses().size() ); + l = metadata.getLicenses().get( 0 ); + assertEquals( "license name", l.getName() ); + assertEquals( "license url", l.getUrl() ); + + assertEquals( 1, metadata.getDependencies().size() ); + d = metadata.getDependencies().get( 0 ); + assertEquals( "artifactId", d.getArtifactId() ); + assertEquals( "classifier", d.getClassifier() ); + assertEquals( "groupId", d.getGroupId() ); + assertEquals( "scope", d.getScope() ); + assertEquals( "system path", d.getSystemPath() ); + assertEquals( "type", d.getType() ); + assertEquals( "version", d.getVersion() ); + } + + public void testUpdateProjectReference() + { + ProjectVersionReference reference = new ProjectVersionReference(); + reference.setNamespace( "another.namespace" ); + reference.setProjectId( "another-project-id" ); + reference.setProjectVersion( "1.1" ); + reference.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY ); + + repository.updateProjectReference( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, reference ); + + Collection references = repository.getProjectReferences( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); + assertEquals( 1, references.size() ); + reference = references.iterator().next(); + assertEquals( "another.namespace", reference.getNamespace() ); + assertEquals( "another-project-id", reference.getProjectId() ); + assertEquals( "1.1", reference.getProjectVersion() ); + assertEquals( ProjectVersionReference.ReferenceType.DEPENDENCY, reference.getReferenceType() ); + } + + public void testGetRepositories() + { + // currently set up this way so the behaviour of both the test and the mock config return the same repository + // set as the File implementation just uses the config rather than the content + repository.updateNamespace( TEST_REPO_ID, "namespace" ); + repository.updateNamespace( "other-repo", "namespace" ); + + assertEquals( Arrays.asList( TEST_REPO_ID, "other-repo" ), new ArrayList( repository.getRepositories() ) ); } public void testUpdateProjectVersionMetadataIncomplete() @@ -123,6 +338,17 @@ public abstract class AbstractMetadataRepositoryTest metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ); assertEquals( true, metadata.isIncomplete() ); + assertNull( metadata.getCiManagement() ); + assertNull( metadata.getScm() ); + assertNull( metadata.getIssueManagement() ); + assertNull( metadata.getOrganization() ); + assertNull( metadata.getDescription() ); + assertNull( metadata.getName() ); + assertEquals( TEST_PROJECT_VERSION, metadata.getId() ); + assertEquals( TEST_PROJECT_VERSION, metadata.getVersion() ); + assertTrue( metadata.getMailingLists().isEmpty() ); + assertTrue( metadata.getLicenses().isEmpty() ); + assertTrue( metadata.getDependencies().isEmpty() ); } public void testUpdateProjectVersionMetadataWithExistingFacets() @@ -311,9 +537,16 @@ public abstract class AbstractMetadataRepositoryTest public void testRemoveFacetsWhenUnknown() { + // testing no exception repository.removeMetadataFacets( TEST_REPO_ID, UNKNOWN ); } + public void testRemoveFacetWhenUnknown() + { + // testing no exception + repository.removeMetadataFacet( TEST_REPO_ID, UNKNOWN, TEST_NAME ); + } + public void testRemoveFacet() { TestMetadataFacet metadataFacet = new TestMetadataFacet( TEST_VALUE ); @@ -343,11 +576,6 @@ public abstract class AbstractMetadataRepositoryTest assertNull( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) ); } - public void testRemoveFacetWhenUnknown() - { - repository.removeMetadataFacet( TEST_REPO_ID, UNKNOWN, TEST_NAME ); - } - public void testGetArtifacts() { ArtifactMetadata artifact1 = createArtifact(); @@ -405,8 +633,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeOpen() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -417,8 +643,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeSparseNamespace() { String namespace = "org.apache.archiva"; - repository.updateNamespace( TEST_REPO_ID, namespace ); - repository.updateProject( TEST_REPO_ID, createProject( namespace ) ); ArtifactMetadata artifact = createArtifact(); artifact.setNamespace( namespace ); repository.updateArtifact( TEST_REPO_ID, namespace, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -429,8 +653,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeLowerBound() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -441,8 +663,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeLowerBoundOutOfRange() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -452,8 +672,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeLowerAndUpperBound() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -465,8 +683,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeUpperBound() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -477,8 +693,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByDateRangeUpperBoundOutOfRange() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -488,13 +702,9 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByRepoId() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); - assertFalse( repository.getArtifacts(TEST_REPO_ID).isEmpty()); - - + assertEquals( Collections.singletonList( artifact ), repository.getArtifacts( TEST_REPO_ID ) ); } public void testGetNamespacesWithSparseDepth() @@ -509,8 +719,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByChecksumSingleResultMd5() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -520,8 +728,6 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByChecksumSingleResultSha1() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); @@ -529,19 +735,23 @@ public abstract class AbstractMetadataRepositoryTest TEST_SHA1 ) ); } + public void testGetArtifactsByChecksumDeepNamespace() + { + ArtifactMetadata artifact = createArtifact(); + String namespace = "multi.level.ns"; + artifact.setNamespace( namespace ); + repository.updateArtifact( TEST_REPO_ID, namespace, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); + + assertEquals( Collections.singletonList( artifact ), repository.getArtifactsByChecksum( TEST_REPO_ID, + TEST_SHA1 ) ); + } + public void testGetArtifactsByChecksumMultipleResult() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - - ProjectMetadata projectMetadata = createProject(); - repository.updateProject( TEST_REPO_ID, projectMetadata ); ArtifactMetadata artifact1 = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact1 ); - projectMetadata = createProject(); String newProjectId = "another-project"; - projectMetadata.setId( newProjectId ); - repository.updateProject( TEST_REPO_ID, projectMetadata ); ArtifactMetadata artifact2 = createArtifact(); artifact2.setProject( newProjectId ); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, newProjectId, TEST_PROJECT_VERSION, artifact2 ); @@ -554,18 +764,17 @@ public abstract class AbstractMetadataRepositoryTest public void testGetArtifactsByChecksumNoResult() { - repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE ); - repository.updateProject( TEST_REPO_ID, createProject() ); ArtifactMetadata artifact = createArtifact(); repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); - assertEquals( Collections.emptyList(), repository.getArtifactsByChecksum( TEST_REPO_ID, - "not a checksum" ) ); + assertEquals( Collections.emptyList(), repository.getArtifactsByChecksum( TEST_REPO_ID, "not a checksum" ) ); } public void testDeleteArtifact() { ArtifactMetadata artifact = createArtifact(); + artifact.addFacet( new TestMetadataFacet( "value" ) ); + repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact ); assertEquals( Collections.singletonList( artifact ), new ArrayList( repository.getArtifacts( @@ -608,15 +817,16 @@ public abstract class AbstractMetadataRepositoryTest repository.deleteRepository( TEST_REPO_ID ); - assertTrue( repository.getArtifactsByDateRange( TEST_REPO_ID, null, null ).isEmpty() ); + assertTrue( repository.getArtifacts( TEST_REPO_ID ).isEmpty() ); + assertTrue( repository.getRootNamespaces( TEST_REPO_ID ).isEmpty() ); } - private ProjectMetadata createProject() + private static ProjectMetadata createProject() { return createProject( TEST_NAMESPACE ); } - private ProjectMetadata createProject( String ns ) + private static ProjectMetadata createProject( String ns ) { ProjectMetadata project = new ProjectMetadata(); project.setId( TEST_PROJECT ); @@ -624,12 +834,12 @@ public abstract class AbstractMetadataRepositoryTest return project; } - private ArtifactMetadata createArtifact() + private static ArtifactMetadata createArtifact() { return createArtifact( "jar" ); } - private ArtifactMetadata createArtifact( String type ) + private static ArtifactMetadata createArtifact( String type ) { ArtifactMetadata artifact = new ArtifactMetadata(); artifact.setId( TEST_PROJECT + "-" + TEST_PROJECT_VERSION + "." + type ); @@ -645,7 +855,7 @@ public abstract class AbstractMetadataRepositoryTest return artifact; } - private class ArtifactMetadataComparator + private static class ArtifactMetadataComparator implements Comparator { public final int compare( ArtifactMetadata a, ArtifactMetadata b ) diff --git a/archiva-modules/plugins/metadata-repository-file/src/main/java/org/apache/archiva/metadata/repository/file/FileMetadataRepository.java b/archiva-modules/plugins/metadata-repository-file/src/main/java/org/apache/archiva/metadata/repository/file/FileMetadataRepository.java index e4e1f83b6..d03f756ba 100644 --- a/archiva-modules/plugins/metadata-repository-file/src/main/java/org/apache/archiva/metadata/repository/file/FileMetadataRepository.java +++ b/archiva-modules/plugins/metadata-repository-file/src/main/java/org/apache/archiva/metadata/repository/file/FileMetadataRepository.java @@ -133,7 +133,7 @@ public class FileMetadataRepository Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); // remove properties that are not references or artifacts - for ( Object key : new ArrayList( properties.keySet() ) ) + for ( Object key : new ArrayList( properties.keySet() ) ) { String name = (String) key; if ( !name.contains( ":" ) && !name.equals( "facetIds" ) ) @@ -579,7 +579,7 @@ public class FileMetadataRepository properties.remove( "artifact:facetIds:" + id ); String prefix = "artifact:facet:" + id + ":"; - for ( Object key : new ArrayList( properties.keySet() ) ) + for ( Object key : new ArrayList( properties.keySet() ) ) { String property = (String) key; if ( property.startsWith( prefix ) ) @@ -666,6 +666,10 @@ public class FileMetadataRepository public void updateArtifact( String repoId, String namespace, String projectId, String projectVersion, ArtifactMetadata artifact ) { + 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 ); @@ -741,11 +745,18 @@ public class FileMetadataRepository { File directory = new File( getDirectory( repoId ), namespace + "/" + projectId ); - Properties properties = readOrCreateProperties( directory, PROJECT_VERSION_METADATA_KEY ); + 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 ); + } - ProjectMetadata project = new ProjectMetadata(); - project.setNamespace( properties.getProperty( "namespace" ) ); - project.setId( properties.getProperty( "id" ) ); return project; } @@ -838,8 +849,15 @@ public class FileMetadataRepository MailingList mailingList = new MailingList(); mailingList.setName( mailingListName ); mailingList.setMainArchiveUrl( properties.getProperty( "mailingList." + i + ".archive" ) ); - mailingList.setOtherArchives( Arrays.asList( properties.getProperty( - "mailingList." + i + ".otherArchives" ).split( "," ) ) ); + String p = properties.getProperty( "mailingList." + i + ".otherArchives" ); + if ( p != null && p.length() > 0 ) + { + mailingList.setOtherArchives( Arrays.asList( p.split( "," ) ) ); + } + else + { + mailingList.setOtherArchives( Collections.emptyList() ); + } mailingList.setPostAddress( properties.getProperty( "mailingList." + i + ".post" ) ); mailingList.setSubscribeAddress( properties.getProperty( "mailingList." + i + ".subscribe" ) ); mailingList.setUnsubscribeAddress( properties.getProperty( "mailingList." + i + ".unsubscribe" ) ); @@ -1078,7 +1096,6 @@ public class FileMetadataRepository public List getArtifacts( String repoId ) { - List artifacts = new ArrayList(); for ( String ns : getRootNamespaces( repoId ) ) { @@ -1100,9 +1117,7 @@ public class FileMetadataRepository { for ( ArtifactMetadata artifact : getArtifacts( repoId, ns, project, version ) ) { - artifacts.add( artifact ); - } } } diff --git a/archiva-modules/plugins/metadata-store-jcr/pom.xml b/archiva-modules/plugins/metadata-store-jcr/pom.xml new file mode 100644 index 000000000..72ee07e42 --- /dev/null +++ b/archiva-modules/plugins/metadata-store-jcr/pom.xml @@ -0,0 +1,80 @@ + + + + + 4.0.0 + + plugins + org.apache.archiva + 1.4-SNAPSHOT + + metadata-store-jcr + JCR Storage for Metadata + + + org.apache.archiva + metadata-repository-api + + + org.apache.archiva + metadata-repository-api + tests + test + + + org.slf4j + slf4j-simple + test + + + org.codehaus.plexus + plexus-spring + test + + + + javax.jcr + jcr + 2.0 + + + org.apache.jackrabbit + jackrabbit-core + ${jackrabbit.version} + + + + commons-logging + commons-logging + + + org.apache.derby + derby + + + org.apache.jackrabbit + jackrabbit-text-extractors + + + + + diff --git a/archiva-modules/plugins/metadata-store-jcr/src/main/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepository.java b/archiva-modules/plugins/metadata-store-jcr/src/main/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepository.java new file mode 100644 index 000000000..3cf18cf3f --- /dev/null +++ b/archiva-modules/plugins/metadata-store-jcr/src/main/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepository.java @@ -0,0 +1,1245 @@ +package org.apache.archiva.metadata.repository.jcr; + +/* + * 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.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.MetadataResolutionException; +import org.apache.jackrabbit.core.TransientRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.jcr.LoginException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +/** + * @plexus.component role="org.apache.archiva.metadata.repository.MetadataRepository" + * @todo path construction should be centralised + * @todo review all methods for alternate implementations (use of queries) + * @todo below: exception handling + * @todo below: revise storage format for project version metadata + */ +public class JcrMetadataRepository + implements MetadataRepository +{ + /** + * @plexus.requirement role="org.apache.archiva.metadata.model.MetadataFacetFactory" + */ + private Map metadataFacetFactories; + + private static final Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class ); + + private static Repository repository; + + private Session session; + + public JcrMetadataRepository() + { + // TODO: need to close this at the end - do we need to add it in the API? + + try + { + // TODO: push this in from the test, and make it possible from the webapp + if ( repository == null ) + { + repository = new TransientRepository( new File( "src/test/repository.xml" ), new File( "target/jcr" ) ); + } + // TODO: shouldn't do this in constructor since it's a singleton + session = repository.login( new SimpleCredentials( "username", "password".toCharArray() ) ); + } + catch ( LoginException e ) + { + // TODO + throw new RuntimeException( e ); + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public void updateProject( String repositoryId, ProjectMetadata project ) + { + String namespace = project.getNamespace(); + String projectId = project.getId(); + updateProject( repositoryId, namespace, projectId ); + } + + private void updateProject( String repositoryId, String namespace, String projectId ) + { + updateNamespace( repositoryId, namespace ); + + try + { + Node namespaceNode = getOrCreateNamespaceNode( repositoryId, namespace ); + getOrCreateNode( namespaceNode, projectId ); + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion, + ArtifactMetadata artifactMeta ) + { + try + { + Node node = getOrCreateArtifactNode( repositoryId, namespace, projectId, projectVersion, + artifactMeta.getId() ); + + Calendar cal = Calendar.getInstance(); + cal.setTime( artifactMeta.getFileLastModified() ); + node.setProperty( "updated", cal ); + + cal = Calendar.getInstance(); + cal.setTime( artifactMeta.getWhenGathered() ); + node.setProperty( "whenGathered", cal ); + + node.setProperty( "size", artifactMeta.getSize() ); + node.setProperty( "md5", artifactMeta.getMd5() ); + node.setProperty( "sha1", artifactMeta.getSha1() ); + + node.setProperty( "version", artifactMeta.getVersion() ); + + // TODO: namespaced properties instead? + Node facetNode = getOrCreateNode( node, "facets" ); + for ( MetadataFacet facet : artifactMeta.getFacetList() ) + { + // TODO: need to clear it? + Node n = getOrCreateNode( facetNode, facet.getFacetId() ); + + for ( Map.Entry entry : facet.toProperties().entrySet() ) + { + n.setProperty( entry.getKey(), entry.getValue() ); + } + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + private Node getOrCreateArtifactNode( String repositoryId, String namespace, String projectId, + String projectVersion, String id ) + throws RepositoryException + { + Node versionNode = getOrCreateProjectVersionNode( repositoryId, namespace, projectId, projectVersion ); + return getOrCreateNode( versionNode, id ); + } + + public void updateProjectVersion( String repositoryId, String namespace, String projectId, + ProjectVersionMetadata versionMetadata ) + { + updateProject( repositoryId, namespace, projectId ); + + try + { + Node versionNode = getOrCreateProjectVersionNode( repositoryId, namespace, projectId, + versionMetadata.getId() ); + + versionNode.setProperty( "name", versionMetadata.getName() ); + versionNode.setProperty( "description", versionMetadata.getDescription() ); + versionNode.setProperty( "url", versionMetadata.getUrl() ); + versionNode.setProperty( "incomplete", versionMetadata.isIncomplete() ); + + // TODO: decide how to treat these in the content repo + if ( versionMetadata.getScm() != null ) + { + versionNode.setProperty( "scm.connection", versionMetadata.getScm().getConnection() ); + versionNode.setProperty( "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() ); + versionNode.setProperty( "scm.url", versionMetadata.getScm().getUrl() ); + } + if ( versionMetadata.getCiManagement() != null ) + { + versionNode.setProperty( "ci.system", versionMetadata.getCiManagement().getSystem() ); + versionNode.setProperty( "ci.url", versionMetadata.getCiManagement().getUrl() ); + } + if ( versionMetadata.getIssueManagement() != null ) + { + versionNode.setProperty( "issue.system", versionMetadata.getIssueManagement().getSystem() ); + versionNode.setProperty( "issue.url", versionMetadata.getIssueManagement().getUrl() ); + } + if ( versionMetadata.getOrganization() != null ) + { + versionNode.setProperty( "org.name", versionMetadata.getOrganization().getName() ); + versionNode.setProperty( "org.url", versionMetadata.getOrganization().getUrl() ); + } + int i = 0; + for ( License license : versionMetadata.getLicenses() ) + { + versionNode.setProperty( "license." + i + ".name", license.getName() ); + versionNode.setProperty( "license." + i + ".url", license.getUrl() ); + i++; + } + i = 0; + for ( MailingList mailingList : versionMetadata.getMailingLists() ) + { + versionNode.setProperty( "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() ); + versionNode.setProperty( "mailingList." + i + ".name", mailingList.getName() ); + versionNode.setProperty( "mailingList." + i + ".post", mailingList.getPostAddress() ); + versionNode.setProperty( "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() ); + versionNode.setProperty( "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() ); + versionNode.setProperty( "mailingList." + i + ".otherArchives", join( + mailingList.getOtherArchives() ) ); + i++; + } + i = 0; + for ( Dependency dependency : versionMetadata.getDependencies() ) + { + versionNode.setProperty( "dependency." + i + ".classifier", dependency.getClassifier() ); + versionNode.setProperty( "dependency." + i + ".scope", dependency.getScope() ); + versionNode.setProperty( "dependency." + i + ".systemPath", dependency.getSystemPath() ); + versionNode.setProperty( "dependency." + i + ".artifactId", dependency.getArtifactId() ); + versionNode.setProperty( "dependency." + i + ".groupId", dependency.getGroupId() ); + versionNode.setProperty( "dependency." + i + ".version", dependency.getVersion() ); + versionNode.setProperty( "dependency." + i + ".type", dependency.getType() ); + i++; + } + + // TODO: namespaced properties instead? + Node facetNode = getOrCreateNode( versionNode, "facets" ); + for ( MetadataFacet facet : versionMetadata.getFacetList() ) + { + // TODO: shouldn't need to recreate, just update + if ( facetNode.hasNode( facet.getFacetId() ) ) + { + facetNode.getNode( facet.getFacetId() ).remove(); + } + Node n = facetNode.addNode( facet.getFacetId() ); + + for ( Map.Entry entry : facet.toProperties().entrySet() ) + { + n.setProperty( entry.getKey(), entry.getValue() ); + } + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + private Node getOrCreateProjectVersionNode( String repositoryId, String namespace, String projectId, + String projectVersion ) + throws RepositoryException + { + Node namespaceNode = getOrCreateNamespaceNode( repositoryId, namespace ); + Node projectNode = getOrCreateNode( namespaceNode, projectId ); + return getOrCreateNode( projectNode, projectVersion ); + } + + private Node getOrCreateNode( Node baseNode, String name ) + throws RepositoryException + { + return baseNode.hasNode( name ) ? baseNode.getNode( name ) : baseNode.addNode( name ); + } + + private Node getOrCreateNamespaceNode( String repositoryId, String namespace ) + throws RepositoryException + { + Node repo = getOrCreateRepositoryContentNode( repositoryId ); + return getOrCreateNode( repo, namespace ); + } + + private Node getOrCreateRepositoryContentNode( String repositoryId ) + throws RepositoryException + { + Node node = getOrCreateRepositoryNode( repositoryId ); + return getOrCreateNode( node, "content" ); + } + + private Node getOrCreateRepositoryNode( String repositoryId ) + throws RepositoryException + { + Node root = session.getRootNode(); + Node node = getOrCreateNode( root, "repositories" ); + node = getOrCreateNode( node, repositoryId ); + return node; + } + + public void updateProjectReference( String repositoryId, String namespace, String projectId, String projectVersion, + ProjectVersionReference reference ) + { + // TODO: try weak reference? + // TODO: is this tree the right way up? It differs from the content model + try + { + Node node = getOrCreateRepositoryContentNode( repositoryId ); + node = getOrCreateNode( node, namespace ); + node = getOrCreateNode( node, projectId ); + node = getOrCreateNode( node, projectVersion ); + node = getOrCreateNode( node, "references" ); + node = getOrCreateNode( node, reference.getNamespace() ); + node = getOrCreateNode( node, reference.getProjectId() ); + node = getOrCreateNode( node, reference.getProjectVersion() ); + node.setProperty( "type", reference.getReferenceType().toString() ); + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public void updateNamespace( String repositoryId, String namespace ) + { + try + { + Node node = getOrCreateNamespaceNode( repositoryId, namespace ); + node.setProperty( "namespace", namespace ); + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public List getMetadataFacets( String repositoryId, String facetId ) + { + List facets = new ArrayList(); + + try + { + Node root = session.getRootNode(); + Node node = root.getNode( "repositories/" + repositoryId + "/facets/" + facetId ); + + // TODO: could we simply query all nodes with no children? + recurse( facets, "", node ); + } + catch ( PathNotFoundException e ) + { + // TODO: handle this case differently? + // currently ignored + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + return facets; + } + + private void recurse( List facets, String prefix, Node node ) + throws RepositoryException + { + NodeIterator iterator = node.getNodes(); + while ( iterator.hasNext() ) + { + Node n = iterator.nextNode(); + String name = prefix + "/" + n.getName(); + if ( n.hasNodes() ) + { + recurse( facets, name, n ); + } + else + { + // strip leading / first + facets.add( name.substring( 1 ) ); + } + } + } + + + public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name ) + { + MetadataFacet metadataFacet = null; + try + { + Node root = session.getRootNode(); + Node node = root.getNode( "repositories/" + repositoryId + "/facets/" + facetId + "/" + name ); + + MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId ); + if ( metadataFacetFactory != null ) + { + metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name ); + Map map = new HashMap(); + PropertyIterator iterator = node.getProperties(); + while ( iterator.hasNext() ) + { + Property property = iterator.nextProperty(); + String p = property.getName(); + if ( !p.startsWith( "jcr:" ) ) + { + map.put( p, property.getString() ); + } + } + metadataFacet.fromProperties( map ); + } + } + catch ( PathNotFoundException e ) + { + // TODO: handle this case differently? + // currently ignored + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + return metadataFacet; + } + + public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet ) + { + try + { + Node repo = getOrCreateRepositoryNode( repositoryId ); + Node facets = getOrCreateNode( repo, "facets" ); + + String id = metadataFacet.getFacetId(); + Node facetNode = getOrCreateNode( facets, id ); + + Node node = getOrCreatePath( facetNode, metadataFacet.getName() ); + + for ( Map.Entry entry : metadataFacet.toProperties().entrySet() ) + { + node.setProperty( entry.getKey(), entry.getValue() ); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + private Node getOrCreatePath( Node baseNode, String name ) + throws RepositoryException + { + Node node = baseNode; + for ( String n : name.split( "/" ) ) + { + node = getOrCreateNode( node, n ); + } + return node; + } + + public void removeMetadataFacets( String repositoryId, String facetId ) + { + try + { + Node root = session.getRootNode(); + String path = "repositories/" + repositoryId + "/facets/" + facetId; + // TODO: exception if missing? + if ( root.hasNode( path ) ) + { + root.getNode( path ).remove(); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public void removeMetadataFacet( String repositoryId, String facetId, String name ) + { + try + { + Node root = session.getRootNode(); + String path = "repositories/" + repositoryId + "/facets/" + facetId + "/" + name; + // TODO: exception if missing? + if ( root.hasNode( path ) ) + { + Node node = root.getNode( path ); + do + { + Node parent = node.getParent(); + node.remove(); + node = parent; + } + while ( !node.hasNodes() ); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public List getArtifactsByDateRange( String repoId, Date startTime, Date endTime ) + { + // 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 artifacts = new ArrayList(); + for ( String ns : getRootNamespaces( repoId ) ) + { + getArtifactsByDateRange( artifacts, repoId, ns, startTime, endTime ); + } + Collections.sort( artifacts, new ArtifactComparator() ); + return artifacts; + } + + private void getArtifactsByDateRange( List artifacts, String repoId, String ns, Date startTime, + Date endTime ) + { + 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 ); + } + } + } + } + } + } + + public Collection getRepositories() + { + List repositories; + + try + { + Node root = session.getRootNode(); + if ( root.hasNode( "repositories" ) ) + { + Node node = root.getNode( "repositories" ); + + repositories = new ArrayList(); + NodeIterator i = node.getNodes(); + while ( i.hasNext() ) + { + Node n = i.nextNode(); + repositories.add( n.getName() ); + } + } + else + { + repositories = Collections.emptyList(); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + return repositories; + } + + public List getArtifactsByChecksum( String repositoryId, String checksum ) + { + // 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 artifacts = new ArrayList(); + for ( String ns : getRootNamespaces( repositoryId ) ) + { + getArtifactsByChecksum( artifacts, repositoryId, ns, checksum ); + } + return artifacts; + } + + private void getArtifactsByChecksum( List artifacts, String repositoryId, String ns, + String checksum ) + { + 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 ); + } + } + } + } + } + + public void deleteArtifact( String repositoryId, String namespace, String projectId, String projectVersion, + String id ) + { + try + { + Node root = session.getRootNode(); + String path = + "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion + + "/" + id; + // TODO: exception if missing? + if ( root.hasNode( path ) ) + { + root.getNode( path ).remove(); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public void deleteRepository( String repositoryId ) + { + try + { + Node root = session.getRootNode(); + String path = "repositories/" + repositoryId; + // TODO: exception if missing? + if ( root.hasNode( path ) ) + { + root.getNode( path ).remove(); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + } + + public List getArtifacts( String repositoryId ) + { + // TODO: query faster? + List artifacts = new ArrayList(); + for ( String ns : getRootNamespaces( repositoryId ) ) + { + getArtifacts( artifacts, repositoryId, ns ); + } + return artifacts; + } + + private void getArtifacts( List artifacts, String repoId, String ns ) + { + 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 ); + } + } + } + } + + public ProjectMetadata getProject( String repositoryId, String namespace, String projectId ) + { + ProjectMetadata metadata = null; + + try + { + Node root = session.getRootNode(); + + // basically just checking it exists + String path = "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId; + if ( root.hasNode( path ) ) + { + metadata = new ProjectMetadata(); + metadata.setId( projectId ); + metadata.setNamespace( namespace ); + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return metadata; + } + + public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId, + String projectVersion ) + throws MetadataResolutionException + { + ProjectVersionMetadata versionMetadata; + + try + { + Node root = session.getRootNode(); + + String path = + "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion; + if ( !root.hasNode( path ) ) + { + return null; + } + + Node node = root.getNode( path ); + + versionMetadata = new ProjectVersionMetadata(); + versionMetadata.setId( projectVersion ); + versionMetadata.setName( getPropertyString( node, "name" ) ); + versionMetadata.setDescription( getPropertyString( node, "description" ) ); + versionMetadata.setUrl( getPropertyString( node, "url" ) ); + versionMetadata.setIncomplete( node.hasProperty( "incomplete" ) && node.getProperty( + "incomplete" ).getBoolean() ); + + // TODO: decide how to treat these in the content repo + String scmConnection = getPropertyString( node, "scm.connection" ); + String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" ); + String scmUrl = getPropertyString( node, "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 = getPropertyString( node, "ci.system" ); + String ciUrl = getPropertyString( node, "ci.url" ); + if ( ciSystem != null || ciUrl != null ) + { + CiManagement ci = new CiManagement(); + ci.setSystem( ciSystem ); + ci.setUrl( ciUrl ); + versionMetadata.setCiManagement( ci ); + } + + String issueSystem = getPropertyString( node, "issue.system" ); + String issueUrl = getPropertyString( node, "issue.url" ); + if ( issueSystem != null || issueUrl != null ) + { + IssueManagement issueManagement = new IssueManagement(); + issueManagement.setSystem( issueSystem ); + issueManagement.setUrl( issueUrl ); + versionMetadata.setIssueManagement( issueManagement ); + } + + String orgName = getPropertyString( node, "org.name" ); + String orgUrl = getPropertyString( node, "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 = getPropertyString( node, "license." + i + ".name" ); + String licenseUrl = getPropertyString( node, "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 = getPropertyString( node, "mailingList." + i + ".name" ); + if ( mailingListName != null ) + { + MailingList mailingList = new MailingList(); + mailingList.setName( mailingListName ); + mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) ); + String n = "mailingList." + i + ".otherArchives"; + if ( node.hasProperty( n ) ) + { + mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) ); + } + else + { + mailingList.setOtherArchives( Collections.emptyList() ); + } + mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) ); + mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) ); + mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) ); + versionMetadata.addMailingList( mailingList ); + } + else + { + done = true; + } + i++; + } + + done = false; + i = 0; + while ( !done ) + { + String dependencyArtifactId = getPropertyString( node, "dependency." + i + ".artifactId" ); + if ( dependencyArtifactId != null ) + { + Dependency dependency = new Dependency(); + dependency.setArtifactId( dependencyArtifactId ); + dependency.setGroupId( getPropertyString( node, "dependency." + i + ".groupId" ) ); + dependency.setClassifier( getPropertyString( node, "dependency." + i + ".classifier" ) ); + dependency.setOptional( Boolean.valueOf( getPropertyString( node, + "dependency." + i + ".optional" ) ) ); + dependency.setScope( getPropertyString( node, "dependency." + i + ".scope" ) ); + dependency.setSystemPath( getPropertyString( node, "dependency." + i + ".systemPath" ) ); + dependency.setType( getPropertyString( node, "dependency." + i + ".type" ) ); + dependency.setVersion( getPropertyString( node, "dependency." + i + ".version" ) ); + versionMetadata.addDependency( dependency ); + } + else + { + done = true; + } + i++; + } + + if ( node.hasNode( "facets" ) ) + { + NodeIterator j = node.getNode( "facets" ).getNodes(); + + while ( j.hasNext() ) + { + Node facetNode = j.nextNode(); + + MetadataFacetFactory factory = metadataFacetFactories.get( facetNode.getName() ); + if ( factory == null ) + { + log.error( "Attempted to load unknown project version metadata facet: " + facetNode.getName() ); + } + else + { + MetadataFacet facet = factory.createMetadataFacet(); + Map map = new HashMap(); + PropertyIterator iterator = facetNode.getProperties(); + while ( iterator.hasNext() ) + { + Property property = iterator.nextProperty(); + String p = property.getName(); + if ( !p.startsWith( "jcr:" ) ) + { + map.put( p, property.getString() ); + } + } + facet.fromProperties( map ); + versionMetadata.addFacet( facet ); + } + } + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return versionMetadata; + } + + private static String getPropertyString( Node node, String name ) + throws RepositoryException + { + return node.hasProperty( name ) ? node.getProperty( name ).getString() : null; + } + + public Collection getArtifactVersions( String repositoryId, String namespace, String projectId, + String projectVersion ) + { + Set versions = new LinkedHashSet(); + + try + { + Node root = session.getRootNode(); + + Node node = root.getNode( + "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion ); + + NodeIterator iterator = node.getNodes(); + while ( iterator.hasNext() ) + { + Node n = iterator.nextNode(); + + versions.add( n.getProperty( "version" ).getString() ); + } + } + catch ( PathNotFoundException e ) + { + // ignore repo not found for now + // TODO: throw specific exception if repo doesn't exist + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return versions; + } + + public Collection getProjectReferences( String repositoryId, String namespace, + String projectId, String projectVersion ) + { + List references = new ArrayList(); + + try + { + Node root = session.getRootNode(); + + String path = + "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion + + "/references"; + if ( root.hasNode( path ) ) + { + Node node = root.getNode( path ); + + // TODO: use query by reference type + NodeIterator i = node.getNodes(); + while ( i.hasNext() ) + { + Node ns = i.nextNode(); + + NodeIterator j = ns.getNodes(); + + while ( j.hasNext() ) + { + Node project = j.nextNode(); + + NodeIterator k = project.getNodes(); + + while ( k.hasNext() ) + { + Node version = k.nextNode(); + + ProjectVersionReference ref = new ProjectVersionReference(); + ref.setNamespace( ns.getName() ); + ref.setProjectId( project.getName() ); + ref.setProjectVersion( version.getName() ); + String type = version.getProperty( "type" ).getString(); + ref.setReferenceType( ProjectVersionReference.ReferenceType.valueOf( type ) ); + references.add( ref ); + } + } + } + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return references; + } + + public Collection getRootNamespaces( String repositoryId ) + { + return getNamespaces( repositoryId, null ); + } + + private Collection getNodeNames( String path ) + { + List names = new ArrayList(); + + try + { + Node root = session.getRootNode(); + + Node repository = root.getNode( path ); + + NodeIterator nodes = repository.getNodes(); + while ( nodes.hasNext() ) + { + Node node = nodes.nextNode(); + names.add( node.getName() ); + } + } + catch ( PathNotFoundException e ) + { + // ignore repo not found for now + // TODO: throw specific exception if repo doesn't exist + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return names; + } + + public Collection getNamespaces( String repositoryId, String baseNamespace ) + { + // TODO: could be simpler with pathed namespaces, rely on namespace property + Collection allNamespaces = getNodeNames( "repositories/" + repositoryId + "/content" ); + + Set 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 ); + } + + public Collection getProjects( String repositoryId, String namespace ) + { + // TODO: could be simpler with pathed namespaces, rely on namespace property + return getNodeNames( "repositories/" + repositoryId + "/content/" + namespace ); + } + + public Collection getProjectVersions( String repositoryId, String namespace, String projectId ) + { + // TODO: could be simpler with pathed namespaces, rely on namespace property + return getNodeNames( "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId ); + } + + public Collection getArtifacts( String repositoryId, String namespace, String projectId, + String projectVersion ) + { + List artifacts = new ArrayList(); + + try + { + Node root = session.getRootNode(); + String path = + "repositories/" + repositoryId + "/content/" + namespace + "/" + projectId + "/" + projectVersion; + + if ( root.hasNode( path ) ) + { + Node node = root.getNode( path ); + + NodeIterator iterator = node.getNodes(); + while ( iterator.hasNext() ) + { + Node artifactNode = iterator.nextNode(); + + String id = artifactNode.getName(); + + ArtifactMetadata artifact = new ArtifactMetadata(); + artifact.setId( id ); + artifact.setRepositoryId( repositoryId ); + artifact.setNamespace( namespace ); + artifact.setProject( projectId ); + artifact.setProjectVersion( projectVersion ); + artifact.setVersion( artifactNode.hasProperty( "version" ) ? artifactNode.getProperty( + "version" ).getString() : projectVersion ); + + if ( artifactNode.hasProperty( "updated" ) ) + { + artifact.setFileLastModified( artifactNode.getProperty( + "updated" ).getDate().getTimeInMillis() ); + } + + if ( artifactNode.hasProperty( "whenGathered" ) ) + { + artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() ); + } + + if ( artifactNode.hasProperty( "size" ) ) + { + artifact.setSize( artifactNode.getProperty( "size" ).getLong() ); + } + + if ( artifactNode.hasProperty( "md5" ) ) + { + artifact.setMd5( artifactNode.getProperty( "md5" ).getString() ); + } + + if ( artifactNode.hasProperty( "sha1" ) ) + { + artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() ); + } + + if ( artifactNode.hasNode( "facets" ) ) + { + NodeIterator j = artifactNode.getNode( "facets" ).getNodes(); + + while ( j.hasNext() ) + { + Node facetNode = j.nextNode(); + + MetadataFacetFactory factory = metadataFacetFactories.get( facetNode.getName() ); + if ( factory == null ) + { + log.error( "Attempted to load unknown project version metadata facet: " + facetNode.getName() ); + } + else + { + MetadataFacet facet = factory.createMetadataFacet(); + Map map = new HashMap(); + PropertyIterator i = facetNode.getProperties(); + while ( i.hasNext() ) + { + Property p = i.nextProperty(); + String property = p.getName(); + map.put( property, p.getString() ); + } + facet.fromProperties( map ); + artifact.addFacet( facet ); + } + } + } + artifacts.add( artifact ); + } + } + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + + return artifacts; + } + + void close() + { + try + { + // TODO: this shouldn't be here! Repository may need a context + session.save(); + } + catch ( RepositoryException e ) + { + // TODO + throw new RuntimeException( e ); + } + session.logout(); + } + + public void setMetadataFacetFactories( Map metadataFacetFactories ) + { + this.metadataFacetFactories = metadataFacetFactories; + + // TODO: check if actually called by normal injection + +// for ( String facetId : metadataFacetFactories.keySet() ) +// { +// // TODO: second arg should be a better URL for the namespace +// session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId ); +// } + } + + private static class ArtifactComparator + implements Comparator + { + 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() ); + } + } + + private String join( Collection 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 null; + } +} diff --git a/archiva-modules/plugins/metadata-store-jcr/src/test/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepositoryTest.java b/archiva-modules/plugins/metadata-store-jcr/src/test/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepositoryTest.java new file mode 100644 index 000000000..aff5d20b6 --- /dev/null +++ b/archiva-modules/plugins/metadata-store-jcr/src/test/java/org/apache/archiva/metadata/repository/jcr/JcrMetadataRepositoryTest.java @@ -0,0 +1,58 @@ +package org.apache.archiva.metadata.repository.jcr; + +/* + * 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.MetadataFacetFactory; +import org.apache.archiva.metadata.repository.AbstractMetadataRepositoryTest; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.util.Map; + +public class JcrMetadataRepositoryTest + extends AbstractMetadataRepositoryTest +{ + private JcrMetadataRepository jcrMetadataRepository; + + public void setUp() + throws Exception + { + super.setUp(); + + File directory = getTestFile( "target/test-repositories" ); + FileUtils.deleteDirectory( directory ); + + Map factories = createTestMetadataFacetFactories(); + + jcrMetadataRepository = new JcrMetadataRepository(); + jcrMetadataRepository.setMetadataFacetFactories( factories ); + + this.repository = jcrMetadataRepository; + } + + @Override + protected void tearDown() + throws Exception + { + super.tearDown(); + + jcrMetadataRepository.close(); + } +} diff --git a/archiva-modules/plugins/metadata-store-jcr/src/test/repository.xml b/archiva-modules/plugins/metadata-store-jcr/src/test/repository.xml new file mode 100644 index 000000000..fcb4b6690 --- /dev/null +++ b/archiva-modules/plugins/metadata-store-jcr/src/test/repository.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archiva-modules/plugins/pom.xml b/archiva-modules/plugins/pom.xml index 60064b8f0..bb7e2b157 100644 --- a/archiva-modules/plugins/pom.xml +++ b/archiva-modules/plugins/pom.xml @@ -37,5 +37,6 @@ maven1-repository stage-repository-merge generic-metadata-support + metadata-store-jcr diff --git a/pom.xml b/pom.xml index a73c02491..783e004fc 100644 --- a/pom.xml +++ b/pom.xml @@ -241,7 +241,7 @@ org.apache.jackrabbit jackrabbit-webdav - 1.5.0 + ${jackrabbit.version} commons-logging @@ -315,6 +315,11 @@ 1.4-SNAPSHOT tests + + org.apache.archiva + metadata-store-jcr + 1.4-SNAPSHOT + org.apache.archiva metadata-repository-file @@ -1167,6 +1172,7 @@ 1.5.8 0.9 2.5.6 + 2.2.0