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 static org.assertj.core.api.Assertions.assertThat;

import java.text.SimpleDateFormat;
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.TimeZone;

import junit.framework.TestCase;

import org.apache.archiva.metadata.generic.GenericMetadataFacet;
import org.apache.archiva.metadata.generic.GenericMetadataFacetFactory;
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.test.utils.ArchivaSpringJUnit4ClassRunner;
import org.assertj.core.util.Sets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;

@RunWith( ArchivaSpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
public abstract class AbstractMetadataRepositoryTest
    extends TestCase
{
    protected static final String OTHER_REPO_ID = "other-repo";

    protected MetadataRepository repository;

    protected static final String TEST_REPO_ID = "test";

    protected static final String TEST_PROJECT = "projectId";

    protected static final String TEST_NAMESPACE = "namespace";

    protected static final String TEST_PROJECT_VERSION = "1.0";

    private static final String TEST_PROJECT_VERSION_2_0 = "2.0";

    private static final String TEST_URL = "http://archiva.apache.org";

    private static final Organization TEST_ORGANIZATION = new Organization( "Apache", "http://apache.org" );

    private static final String TEST_FACET_ID = "test-facet-id";

    private static final String TEST_NAME = "test/name";

    private static final String TEST_VALUE = "test-value";

    private static final String UNKNOWN = "unknown";

    private static final String TEST_MD5 = "bd4a9b642562547754086de2dab26b7d";

    private static final String TEST_SHA1 = "2e5daf0201ddeb068a62d5e08da18657ab2c6be9";

    private static final String TEST_METADATA_KEY = "testkey";

    private static final String TEST_METADATA_VALUE = "testmetadata";

    protected Logger log = LoggerFactory.getLogger( getClass() );

    protected static Map<String, MetadataFacetFactory> createTestMetadataFacetFactories()
    {
        Map<String, MetadataFacetFactory> factories = new HashMap<>();
        factories.put( TEST_FACET_ID, new MetadataFacetFactory()
        {
            @Override
            public MetadataFacet createMetadataFacet()
            {
                return new TestMetadataFacet( TEST_METADATA_VALUE );
            }

            @Override
            public MetadataFacet 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()
            {
                return new TestMetadataFacet( "", TEST_VALUE );
            }

            @Override
            public MetadataFacet createMetadataFacet( String repositoryId, String name )
            {
                return new TestMetadataFacet( "", TEST_VALUE );
            }
        } );

        // for the getArtifactsByProjectVersionMetadata tests
        factories.put( GenericMetadataFacet.FACET_ID, new GenericMetadataFacetFactory() );

        return factories;
    }

    @Test
    public void testRootNamespaceWithNoMetadataRepository()
        throws Exception
    {
        Collection<String> namespaces = repository.getRootNamespaces( TEST_REPO_ID );
        assertThat( namespaces ).isNotNull().isEmpty();
    }

    @Test
    public void testGetNamespaceOnly()
        throws Exception
    {
        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isEmpty();

        repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE );

        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isNotEmpty().contains(
            TEST_NAMESPACE ).hasSize( 1 );

        repository.removeNamespace( TEST_REPO_ID, TEST_NAMESPACE );

        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isEmpty();

    }

    @Test
    public void testGetProjectOnly()
        throws Exception
    {
        assertNull( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) );
        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isEmpty();

        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

        Collection<String> namespaces = repository.getRootNamespaces( TEST_REPO_ID );

        assertThat( namespaces ).isNotNull().isNotEmpty().contains( TEST_NAMESPACE ).hasSize( 1 );
    }

    @Test
    public void testGetProjectVersionOnly()
        throws Exception
    {
        assertNull( repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ) );
        assertNull( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) );
        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isEmpty();

        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
        Collection<String> namespaces = repository.getRootNamespaces( TEST_REPO_ID );

        assertThat( namespaces ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_NAMESPACE );

        ProjectMetadata projectMetadata = repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );
        assertNotNull( projectMetadata );
        assertEquals( TEST_PROJECT, projectMetadata.getId() );
        assertEquals( TEST_NAMESPACE, projectMetadata.getNamespace() );
    }

    @Test
    public void testGetArtifactOnly()
        throws Exception
    {
        assertThat( new ArrayList<>(
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                     TEST_PROJECT_VERSION ) ) ).isNotNull().isEmpty();
        assertThat(
            repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION ) ).isNull();
        assertThat( repository.getProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ) ).isNull();

        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isEmpty();

        ArtifactMetadata metadata = createArtifact();

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        Collection<ArtifactMetadata> artifacts =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
        //assertEquals( Collections.singletonList( metadata ), new ArrayList<ArtifactMetadata>( artifacts ) );
        assertThat( artifacts ).containsExactly( metadata );
        // test that namespace, project and project version is also constructed

        assertThat( repository.getRootNamespaces( TEST_REPO_ID ) ).isNotNull().isNotEmpty().contains(
            TEST_NAMESPACE ).hasSize( 1 );

        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() );
    }

    @Test
    public void testUpdateProjectVersionMetadataWithNoOtherArchives()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );
        MailingList mailingList = new MailingList();
        mailingList.setName( "Foo List" );
        mailingList.setOtherArchives( Collections.<String>emptyList() );
        metadata.setMailingLists( Arrays.asList( 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() );

        List<MailingList> mailingLists = metadata.getMailingLists();

        assertThat( mailingLists ).isNotNull().isNotEmpty().hasSize( 1 );

        mailingList = metadata.getMailingLists().get( 0 );
        assertEquals( "Foo List", mailingList.getName() );

        List<String> others = mailingList.getOtherArchives();
        assertThat( others ).isNotNull().isEmpty();
    }

    @Test
    public void testUpdateProjectVersionMetadataWithAllElements()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );

        metadata.setName( "project name" );
        metadata.setDescription( "project description" );
        metadata.setUrl( "the url" );

        MailingList mailingList = new MailingList();
        mailingList.setName( "Foo List" );
        mailingList.setUnsubscribeAddress( "UnsubscribeAddress" );
        mailingList.setSubscribeAddress( "SubscribeAddress" );
        mailingList.setPostAddress( "PostAddress" );
        mailingList.setMainArchiveUrl( "MainArchiveUrl" );
        mailingList.setOtherArchives( Arrays.asList( "other archive" ) );
        metadata.setMailingLists( Arrays.asList( 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 );

        metadata.setOrganization( TEST_ORGANIZATION );

        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" );
        d.setOptional( true );
        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( "the url", metadata.getUrl() );

        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( TEST_ORGANIZATION.getName(), metadata.getOrganization().getName() );
        assertEquals( TEST_ORGANIZATION.getUrl(), metadata.getOrganization().getUrl() );

        assertEquals( 1, metadata.getMailingLists().size() );
        MailingList retrievedMailingList = metadata.getMailingLists().get( 0 );
        assertEquals( mailingList.getName(), retrievedMailingList.getName() );
        assertEquals( mailingList.getMainArchiveUrl(), retrievedMailingList.getMainArchiveUrl() );
        assertEquals( mailingList.getPostAddress(), retrievedMailingList.getPostAddress() );
        assertEquals( mailingList.getSubscribeAddress(), retrievedMailingList.getSubscribeAddress() );
        assertEquals( mailingList.getUnsubscribeAddress(), retrievedMailingList.getUnsubscribeAddress() );
        assertThat( retrievedMailingList.getOtherArchives() ) //
            .isNotNull() //
            .isNotEmpty() //
            .hasSize( 1 ) //
            .contains( "other archive" );

        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() );
        assertTrue( d.isOptional() );
    }

    @Test
    public void testGetRepositories()
        throws Exception
    {
        // 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_ID, "namespace" );

        Collection<String> repositories = repository.getRepositories();

        assertEquals( "repository.getRepositories() -> " + repositories, //
                      Sets.newLinkedHashSet( TEST_REPO_ID, OTHER_REPO_ID ), //
                      new LinkedHashSet<String>( repositories ) ); //
    }

    @Test
    public void testUpdateProjectVersionMetadataIncomplete()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );
        metadata.setIncomplete( true );
        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );

        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() );
    }

    @Test
    public void testUpdateProjectVersionMetadataWithExistingFacets()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );
        MetadataFacet facet = new TestMetadataFacet( "baz" );
        metadata.addFacet( facet );
        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );

        metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
        assertEquals( Collections.singleton( TEST_FACET_ID ), metadata.getFacetIds() );

        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( Collections.singleton( TEST_FACET_ID ), metadata.getFacetIds() );
        TestMetadataFacet testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );
        assertEquals( "baz", testFacet.getValue() );
    }

    @Test
    public void testUpdateProjectVersionMetadataWithNoExistingFacets()
        throws Exception
    {
        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 );

        assertThat( metadata.getFacetIds() ).isNotNull().isEmpty();

        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 );
        assertThat( metadata.getFacetIds() ).isNotNull().isEmpty();

    }

    @Test
    public void testUpdateProjectVersionMetadataWithExistingFacetsFacetPropertyWasRemoved()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );

        Map<String, String> additionalProps = new HashMap<>();
        additionalProps.put( "deleteKey", "deleteValue" );

        MetadataFacet facet = new TestMetadataFacet( TEST_FACET_ID, "baz", additionalProps );
        metadata.addFacet( facet );
        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );

        metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( metadata.getFacetIds() ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_FACET_ID );

        TestMetadataFacet testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );
        Map<String, String> facetProperties = testFacet.toProperties();

        assertEquals( "deleteValue", facetProperties.get( "deleteKey" ) );

        facetProperties.remove( "deleteKey" );

        TestMetadataFacet newTestFacet = new TestMetadataFacet( TEST_FACET_ID, testFacet.getValue(), facetProperties );
        metadata.addFacet( newTestFacet );

        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );

        metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( metadata.getFacetIds() ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_FACET_ID );
        testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );
        assertFalse( testFacet.toProperties().containsKey( "deleteKey" ) );
    }

    @Test
    public void testGetArtifactsDoesntReturnProjectVersionMetadataFacets()
        throws Exception
    {
        ProjectVersionMetadata versionMetadata = new ProjectVersionMetadata();
        versionMetadata.setId( TEST_PROJECT_VERSION );

        MetadataFacet facet = new TestMetadataFacet( TEST_FACET_ID, "baz" );
        versionMetadata.addFacet( facet );
        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, versionMetadata );

        ArtifactMetadata artifactMetadata = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifactMetadata );
        repository.save();

        Collection<ArtifactMetadata> artifacts =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
        assertEquals( Collections.singletonList( artifactMetadata ), new ArrayList<>( artifacts ) );

        artifacts = repository.getArtifacts( TEST_REPO_ID );
        assertEquals( Collections.singletonList( artifactMetadata ), new ArrayList<>( artifacts ) );

        artifacts = repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_SHA1 );
        assertEquals( Collections.singletonList( artifactMetadata ), new ArrayList<>( artifacts ) );

        artifacts = repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_MD5 );
        assertEquals( Collections.singletonList( artifactMetadata ), new ArrayList<>( artifacts ) );

        artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, null, null );
        assertEquals( Collections.singletonList( artifactMetadata ), new ArrayList<>( artifacts ) );
    }

    @Test
    public void testUpdateArtifactMetadataWithExistingFacetsFacetPropertyWasRemoved()
        throws Exception
    {
        ArtifactMetadata metadata = createArtifact();

        Map<String, String> additionalProps = new HashMap<>();
        additionalProps.put( "deleteKey", "deleteValue" );

        MetadataFacet facet = new TestMetadataFacet( TEST_FACET_ID, "baz", additionalProps );
        metadata.addFacet( facet );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        Collection<ArtifactMetadata> artifacts =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( artifacts ).isNotNull().isNotEmpty().hasSize( 1 );
        metadata = artifacts.iterator().next();

        Collection<String> ids = metadata.getFacetIds();
        assertThat( ids ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_FACET_ID );

        TestMetadataFacet testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );
        Map<String, String> facetProperties = testFacet.toProperties();

        assertEquals( "deleteValue", facetProperties.get( "deleteKey" ) );

        facetProperties.remove( "deleteKey" );

        TestMetadataFacet newTestFacet = new TestMetadataFacet( TEST_FACET_ID, testFacet.getValue(), facetProperties );
        metadata.addFacet( newTestFacet );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        artifacts = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( artifacts ).isNotNull().isNotEmpty().hasSize( 1 );
        metadata = artifacts.iterator().next();

        ids = metadata.getFacetIds();
        assertThat( ids ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_FACET_ID );

        testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );

        Map<String, String> props = testFacet.toProperties();
        assertThat( props ).isNotNull().doesNotContainKey( "deleteKey" );
    }

    @Test
    public void testUpdateArtifactMetadataWithExistingFacets()
        throws Exception
    {
        ArtifactMetadata metadata = createArtifact();
        MetadataFacet facet = new TestMetadataFacet( "baz" );
        metadata.addFacet( facet );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        metadata = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                            TEST_PROJECT_VERSION ).iterator().next();
        assertEquals( Collections.singleton( TEST_FACET_ID ), metadata.getFacetIds() );

        metadata = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        metadata = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                            TEST_PROJECT_VERSION ).iterator().next();
        assertEquals( Collections.singleton( TEST_FACET_ID ), metadata.getFacetIds() );
        TestMetadataFacet testFacet = (TestMetadataFacet) metadata.getFacet( TEST_FACET_ID );
        assertEquals( "baz", testFacet.getValue() );
    }

    @Test
    public void testUpdateArtifactMetadataWithNoExistingFacets()
        throws Exception
    {
        ArtifactMetadata metadata = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        metadata = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                            TEST_PROJECT_VERSION ).iterator().next();
        assertEquals( Collections.<String>emptyList(), new ArrayList<String>( metadata.getFacetIds() ) );

        metadata = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, metadata );

        metadata = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                            TEST_PROJECT_VERSION ).iterator().next();
        assertEquals( Collections.<String>emptyList(), new ArrayList<String>( metadata.getFacetIds() ) );
    }

    @Test
    public void testGetMetadataFacet()
        throws Exception
    {
        repository.addMetadataFacet( TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) );

        TestMetadataFacet test =
            (TestMetadataFacet) repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME );

        assertEquals( new TestMetadataFacet( TEST_VALUE ), test );
    }

    @Test
    public void testGetMetadataFacetWhenEmpty()
        throws Exception
    {
        assertNull( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) );
    }

    @Test
    public void testGetMetadataFacetWhenUnknownName()
        throws Exception
    {
        repository.addMetadataFacet( TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) );

        assertNull( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, UNKNOWN ) );
    }

    @Test
    public void testGetMetadataFacetWhenDefaultValue()
        throws Exception
    {
        repository.addMetadataFacet( TEST_REPO_ID, new TestMetadataFacet( null ) );

        MetadataFacet metadataFacet = repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME );

        assertEquals( new TestMetadataFacet( TEST_METADATA_VALUE ), metadataFacet );
    }

    @Test
    public void testGetMetadataFacetWhenUnknownFacetId()
        throws Exception
    {
        assertNull( repository.getMetadataFacet( TEST_REPO_ID, UNKNOWN, TEST_NAME ) );
    }

    @Test
    public void testGetMetadataFacets()
        throws Exception
    {
        repository.addMetadataFacet( TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) );

        assertEquals( Collections.singletonList( TEST_NAME ),
                      repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID ) );
    }

    @Test
    public void testGetMetadataFacetsWhenEmpty()
        throws Exception
    {

        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertTrue( facets.isEmpty() );
    }

    @Test
    public void testRemoveFacets()
        throws Exception
    {
        repository.addMetadataFacet( TEST_REPO_ID, new TestMetadataFacet( TEST_VALUE ) );

        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertFalse( facets.isEmpty() );

        repository.removeMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );

        facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertTrue( facets.isEmpty() );
    }

    @Test
    public void testRemoveFacetsWhenEmpty()
        throws Exception
    {
        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertTrue( facets.isEmpty() );

        repository.removeMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );

        facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertTrue( facets.isEmpty() );
    }

    @Test
    public void testRemoveFacetsWhenUnknown()
        throws Exception
    {
        // testing no exception
        repository.removeMetadataFacets( TEST_REPO_ID, UNKNOWN );
    }

    @Test
    public void testRemoveFacetWhenUnknown()
        throws Exception
    {
        // testing no exception
        repository.removeMetadataFacet( TEST_REPO_ID, UNKNOWN, TEST_NAME );
    }

    @Test
    public void testRemoveFacet()
        throws Exception
    {
        TestMetadataFacet metadataFacet = new TestMetadataFacet( TEST_VALUE );
        repository.addMetadataFacet( TEST_REPO_ID, metadataFacet );

        assertEquals( metadataFacet, repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) );
        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertFalse( facets.isEmpty() );

        repository.removeMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME );

        assertNull( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) );
        facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertTrue( facets.isEmpty() );
    }

    @Test
    public void testRemoveFacetWhenEmpty()
        throws Exception
    {
        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertThat( facets ).isNotNull().isEmpty();
        assertThat( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) ).isNull();

        repository.removeMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME );

        facets = repository.getMetadataFacets( TEST_REPO_ID, TEST_FACET_ID );
        assertThat( facets ).isNotNull().isEmpty();
        assertThat( repository.getMetadataFacet( TEST_REPO_ID, TEST_FACET_ID, TEST_NAME ) ).isNull();
    }

    @Test
    public void hasMetadataFacetStart()
        throws Exception
    {
        assertFalse( repository.hasMetadataFacet( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() ) );

    }

    @Test
    public void hasMetadataFacet()
        throws Exception
    {

        assertFalse( repository.hasMetadataFacet( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() ) );

        Calendar cal = Calendar.getInstance();

        repository.addMetadataFacet( TEST_REPO_ID, new KindOfRepositoryStatistics( "first", cal.getTime() ) );

        assertTrue( repository.hasMetadataFacet( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() ) );

        cal.add( Calendar.MINUTE, 2 );

        repository.addMetadataFacet( TEST_REPO_ID, new KindOfRepositoryStatistics( "second", cal.getTime() ) );

        cal.add( Calendar.MINUTE, 2 );

        repository.addMetadataFacet( TEST_REPO_ID, new KindOfRepositoryStatistics( "third", cal.getTime() ) );

        List<String> facets = repository.getMetadataFacets( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() );

        assertThat( facets ).isNotNull().isNotEmpty().hasSize( 3 );

        assertTrue( repository.hasMetadataFacet( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() ) );

        repository.removeMetadataFacets( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() );

        assertFalse( repository.hasMetadataFacet( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() ) );

        facets = repository.getMetadataFacets( TEST_REPO_ID, KindOfRepositoryStatistics.class.getName() );

        assertThat( facets ).isNotNull().isEmpty();

    }


    @Test
    public void testGetArtifacts()
        throws Exception
    {
        ArtifactMetadata artifact1 = createArtifact();
        ArtifactMetadata artifact2 = createArtifact( "pom" );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact1 );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact2 );

        Collection<ArtifactMetadata> artifacts =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
        ArrayList<ArtifactMetadata> actual = new ArrayList<>( artifacts );
        Collections.sort( actual, new Comparator<ArtifactMetadata>()
        {
            @Override
            public int compare( ArtifactMetadata o1, ArtifactMetadata o2 )
            {
                return o1.getId().compareTo( o2.getId() );
            }
        } );
        assertEquals( Arrays.asList( artifact1, artifact2 ), actual );
    }

    @Test
    public void testGetArtifactVersions()
        throws Exception
    {
        ArtifactMetadata artifact1 = createArtifact();
        String version1 = "1.0-20091212.012345-1";
        artifact1.setId( artifact1.getProject() + "-" + version1 + ".jar" );
        artifact1.setVersion( version1 );
        ArtifactMetadata artifact2 = createArtifact();
        String version2 = "1.0-20091212.123456-2";
        artifact2.setId( artifact2.getProject() + "-" + version2 + ".jar" );
        artifact2.setVersion( version2 );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact1 );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact2 );

        Collection<String> versions =
            repository.getArtifactVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( versions ).isNotNull().isNotEmpty().contains( version1, version2 );
    }

    @Test
    public void testGetArtifactVersionsMultipleArtifactsSingleVersion()
        throws Exception
    {
        ArtifactMetadata artifact1 = createArtifact();
        artifact1.setId( TEST_PROJECT + "-" + TEST_PROJECT_VERSION + ".jar" );
        ArtifactMetadata artifact2 = createArtifact();
        artifact2.setId( TEST_PROJECT + "-" + TEST_PROJECT_VERSION + "-sources.jar" );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact1 );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact2 );

        Collection<String> versions =
            repository.getArtifactVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( versions ).isNotNull().isNotEmpty().hasSize( 1 ).containsExactly( TEST_PROJECT_VERSION );

    }

    @Test
    public void testGetArtifactsByDateRangeOpen()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, null, null );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByDateRangeSparseNamespace()
        throws Exception
    {
        String namespace = "org.apache.archiva";
        ArtifactMetadata artifact = createArtifact();
        artifact.setNamespace( namespace );
        repository.updateArtifact( TEST_REPO_ID, namespace, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, null, null );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByDateRangeLowerBound()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        Date date = new Date( artifact.getWhenGathered().getTime() - 10000 );

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, date, null );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByDateRangeLowerBoundOutOfRange()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        Date date = new Date( artifact.getWhenGathered().getTime() + 10000 );

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, date, null );

        assertThat( artifacts ).isNotNull().isEmpty();
    }

    @Test
    public void testGetArtifactsByDateRangeLowerAndUpperBound()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        Date lower = new Date( artifact.getWhenGathered().getTime() - 10000 );
        Date upper = new Date( artifact.getWhenGathered().getTime() + 10000 );

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, lower, upper );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByDateRangeUpperBound()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        Date upper = new Date( artifact.getWhenGathered().getTime() + 10000 );

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, null, upper );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByDateRangeUpperBoundOutOfRange()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        Date upper = new Date( artifact.getWhenGathered().getTime() - 10000 );

        List<ArtifactMetadata> artifacts = repository.getArtifactsByDateRange( TEST_REPO_ID, null, upper );

        assertThat( artifacts ).isNotNull().isEmpty();
    }

    @Test
    public void testGetArtifactsByRepoId()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        List<ArtifactMetadata> artifacts = repository.getArtifacts( TEST_REPO_ID );

        assertEquals( Collections.singletonList( artifact ), artifacts );
    }

    @Test
    public void testGetArtifactsByRepoIdMultipleCopies()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        ArtifactMetadata secondArtifact = createArtifact();
        secondArtifact.setRepositoryId( OTHER_REPO_ID );
        repository.updateArtifact( OTHER_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, secondArtifact );
        repository.save();

        // test it restricts to the appropriate repository
        assertEquals( Collections.singletonList( artifact ), repository.getArtifacts( TEST_REPO_ID ) );
        assertEquals( Collections.singletonList( secondArtifact ), repository.getArtifacts( OTHER_REPO_ID ) );
    }

    @Test
    public void testGetArtifactsByDateRangeMultipleCopies()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        ArtifactMetadata secondArtifact = createArtifact();
        secondArtifact.setRepositoryId( OTHER_REPO_ID );
        repository.updateArtifact( OTHER_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, secondArtifact );
        repository.save();

        // test it restricts to the appropriate repository
        assertEquals( Collections.singletonList( artifact ),
                      repository.getArtifactsByDateRange( TEST_REPO_ID, null, null ) );
        assertEquals( Collections.singletonList( secondArtifact ),
                      repository.getArtifactsByDateRange( OTHER_REPO_ID, null, null ) );
    }

    @Test
    public void testGetArtifactsByChecksumMultipleCopies()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        ArtifactMetadata secondArtifact = createArtifact();
        secondArtifact.setRepositoryId( OTHER_REPO_ID );
        repository.updateArtifact( OTHER_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, secondArtifact );
        repository.save();

        // test it restricts to the appropriate repository
        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_SHA1 ) ) );
        assertEquals( Collections.singletonList( secondArtifact ), new ArrayList<>(
            repository.getArtifactsByChecksum( OTHER_REPO_ID, TEST_SHA1 ) ) );
        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_MD5 ) ) );
        assertEquals( Collections.singletonList( secondArtifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( OTHER_REPO_ID, TEST_MD5 ) ) );
    }

    @Test
    public void testGetNamespacesWithSparseDepth()
        throws Exception
    {
        repository.updateNamespace( TEST_REPO_ID, "org.apache.maven.shared" );

        Collection<String> namespaces = repository.getRootNamespaces( TEST_REPO_ID );

        assertThat( namespaces ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "org" );

        namespaces = repository.getNamespaces( TEST_REPO_ID, "org" );
        assertThat( namespaces ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "apache" );

        namespaces = repository.getNamespaces( TEST_REPO_ID, "org.apache" );
        assertThat( namespaces ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "maven" );

        namespaces = repository.getNamespaces( TEST_REPO_ID, "org.apache.maven" );
        assertThat( namespaces ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "shared" );
    }

    @Test
    public void testGetNamespacesWithProjectsPresent()
        throws Exception
    {
        String namespace = "org.apache.maven.shared";
        repository.updateNamespace( TEST_REPO_ID, namespace );

        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );
        repository.updateProjectVersion( TEST_REPO_ID, namespace, TEST_PROJECT, metadata );

        Collection<String> namespaces = repository.getNamespaces( TEST_REPO_ID, namespace );

        assertThat( namespaces ).isNotNull().isEmpty();

    }

    @Test
    public void testGetProjectsWithOtherNamespacesPresent()
        throws Exception
    {
        ProjectMetadata projectMetadata = new ProjectMetadata();
        projectMetadata.setId( TEST_PROJECT );
        projectMetadata.setNamespace( "org.apache.maven" );
        repository.updateProject( TEST_REPO_ID, projectMetadata );

        repository.updateNamespace( TEST_REPO_ID, "org.apache.maven.shared" );

        Collection<String> projects = repository.getProjects( TEST_REPO_ID, "org.apache.maven" );

        assertThat( projects ).isNotNull().isNotEmpty().hasSize( 1 ).contains( TEST_PROJECT );
    }

    @Test
    public void testGetProjectVersionsWithOtherNamespacesPresent()
        throws Exception
    {
        // an unusual case but technically possible where a project namespace matches another project's name

        ProjectVersionMetadata versionMetadata = new ProjectVersionMetadata();
        versionMetadata.setId( TEST_PROJECT_VERSION );
        repository.updateProjectVersion( TEST_REPO_ID, "org.apache.maven", TEST_PROJECT, versionMetadata );

        repository.updateProjectVersion( TEST_REPO_ID, "org.apache.maven." + TEST_PROJECT, "other-project",
                                         versionMetadata );

        Collection<String> versions =
            repository.getProjectVersions( TEST_REPO_ID, "org.apache.maven." + TEST_PROJECT, "other-project" );
        assertThat( versions ).isNotNull().isNotEmpty().contains( TEST_PROJECT_VERSION );

        versions = repository.getProjectVersions( TEST_REPO_ID, "org.apache.maven", TEST_PROJECT );

        assertThat( versions ).isNotNull().isNotEmpty().contains( TEST_PROJECT_VERSION );
    }

    @Test
    public void testGetArtifactsByChecksumSingleResultMd5()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_MD5 ) ) );
    }

    @Test
    public void testGetArtifactsByChecksumSingleResultSha1()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_SHA1 ) ) );
    }

    @Test
    public void testGetArtifactsByChecksumDeepNamespace()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        String namespace = "multi.level.ns";
        artifact.setNamespace( namespace );
        repository.updateArtifact( TEST_REPO_ID, namespace, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        repository.save();

        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_SHA1 ) ) );
        assertEquals( Collections.singletonList( artifact ),
                      new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_MD5 ) ) );
    }

    @Test
    public void testGetArtifactsByChecksumMultipleResult()
        throws Exception
    {
        ArtifactMetadata artifact1 = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact1 );

        String newProjectId = "another-project";
        ArtifactMetadata artifact2 = createArtifact();
        artifact2.setProject( newProjectId );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, newProjectId, TEST_PROJECT_VERSION, artifact2 );
        repository.save();

        List<ArtifactMetadata> artifacts =
            new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_SHA1 ) );
        Collections.sort( artifacts, new ArtifactMetadataComparator() );
        assertEquals( Arrays.asList( artifact2, artifact1 ), artifacts );

        artifacts = new ArrayList<>( repository.getArtifactsByChecksum( TEST_REPO_ID, TEST_MD5 ) );
        Collections.sort( artifacts, new ArtifactMetadataComparator() );
        assertEquals( Arrays.asList( artifact2, artifact1 ), artifacts );
    }

    @Test
    public void testGetArtifactsByChecksumNoResult()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        Collection<ArtifactMetadata> artifactsByChecksum =
            repository.getArtifactsByChecksum( TEST_REPO_ID, "not checksum" );
        assertThat( artifactsByChecksum ).isNotNull().isEmpty();
    }

    @Test
    public void testGetArtifactsByProjectVersionMetadata()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet( 10 );
        Collection<ArtifactMetadata> artifactsByMetadata =
            repository.getArtifactsByProjectVersionMetadata( TEST_METADATA_KEY, TEST_METADATA_VALUE, TEST_REPO_ID );
        assertThat( artifactsByMetadata ).hasSize( 1 );
        ArtifactMetadata artifactMetadata = artifactsByMetadata.iterator().next();
        assertThat( artifactMetadata.getId() ).isEqualTo( "projectId-1.0.jar" );
        assertThat( artifactMetadata.getSha1() ).isEqualTo( TEST_SHA1 );
        assertThat( artifactMetadata.getRepositoryId() ).isEqualTo( TEST_REPO_ID );
    }

    @Test
    public void testGetArtifactsByProjectVersionMetadataNoRepository()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        Collection<ArtifactMetadata> artifactsByMetadata =
            repository.getArtifactsByProjectVersionMetadata( TEST_METADATA_KEY, TEST_METADATA_VALUE, null );
        assertThat( artifactsByMetadata ).hasSize( 1 );
        assertThat( artifactsByMetadata.iterator().next().getRepositoryId() ).isNotNull().isNotEmpty();
    }

    @Test
    public void testGetArtifactsByProjectVersionMetadataAllRepositories()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        Collection<ArtifactMetadata> artifactsByMetadata =
            repository.getArtifactsByProjectVersionMetadata( TEST_METADATA_KEY, TEST_METADATA_VALUE, null );
        assertThat( artifactsByMetadata ).hasSize( 1 );
    }

    @Test
    public void testGetArtifactsByMetadataAllRepositories()
        throws Exception
    {
        createArtifactWithMavenArtifactFacet();
        Collection<ArtifactMetadata> artifactsByMetadata =
            repository.getArtifactsByMetadata( "foo", TEST_METADATA_VALUE, null );
        assertThat( artifactsByMetadata ).hasSize( 1 );
        ArtifactMetadata artifactMetadata = artifactsByMetadata.iterator().next();
        assertThat( artifactMetadata.getId() ).isEqualTo( "projectId-1.0.jar" );
        assertThat( artifactMetadata.getSha1() ).isEqualTo( TEST_SHA1 );
        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 ) );
    }

    @Test
    public void testGetArtifactsByPropertySingleResult()
        throws Exception
    {
        createArtifactWithData();
        // only works on JCR implementation
        // Collection<ArtifactMetadata> artifactsByProperty = repository.getArtifactsByProperty( "org.name", TEST_ORGANIZATION.getName(), TEST_REPO_ID );
        Collection<ArtifactMetadata> artifactsByProperty = repository.getArtifactsByProperty( "url", TEST_URL, TEST_REPO_ID );
        assertThat( artifactsByProperty ).hasSize( 1 );
        ArtifactMetadata artifactMetadata = artifactsByProperty.iterator().next();
        assertThat( artifactMetadata.getId() ).isEqualTo( "projectId-1.0.jar" );
        assertThat( artifactMetadata.getSha1() ).isEqualTo( TEST_SHA1 );
        assertThat( artifactMetadata.getRepositoryId() ).isEqualTo( TEST_REPO_ID );
    }

    @Test
    public void testDeleteRepository()
        throws Exception
    {
        repository.updateNamespace( TEST_REPO_ID, TEST_NAMESPACE );

        ProjectMetadata project1 = new ProjectMetadata();
        project1.setNamespace( TEST_NAMESPACE );
        project1.setId( "project1" );
        repository.updateProject( TEST_REPO_ID, project1 );
        ProjectMetadata project2 = new ProjectMetadata();
        project2.setNamespace( TEST_NAMESPACE );
        project2.setId( "project2" );
        repository.updateProject( TEST_REPO_ID, project2 );

        ArtifactMetadata artifact1 = createArtifact();
        artifact1.setProject( "project1" );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, "project1", TEST_PROJECT_VERSION, artifact1 );
        ArtifactMetadata artifact2 = createArtifact();
        artifact2.setProject( "project2" );
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, "project2", TEST_PROJECT_VERSION, artifact2 );
        repository.save();

        List<ArtifactMetadata> expected = Arrays.asList( artifact1, artifact2 );
        Collections.sort( expected, new ArtifactMetadataComparator() );

        List<ArtifactMetadata> actual =
            new ArrayList<>( repository.getArtifactsByDateRange( TEST_REPO_ID, null, null ) );
        Collections.sort( actual, new ArtifactMetadataComparator() );

        assertEquals( expected, actual );

        repository.removeRepository( TEST_REPO_ID );

        assertTrue( repository.getArtifacts( TEST_REPO_ID ).isEmpty() );
        assertTrue( repository.getRootNamespaces( TEST_REPO_ID ).isEmpty() );
    }


    @Test
    public void testDeleteArtifact()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        artifact.addFacet( new TestMetadataFacet( "value" ) );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        assertThat( repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                             TEST_PROJECT_VERSION ) ).containsExactly( artifact );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION_2_0, artifact );

        Collection<String> versions = repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        log.info( "versions {}", versions );

        assertThat( versions ).isNotNull().isNotEmpty().hasSize( 2 ).contains( "1.0", "2.0" );

        repository.removeArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact.getId() );

        versions = repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        log.info( "versions {}", versions );

        assertThat( versions ).isNotNull().isNotEmpty().hasSize( 1 ).contains( "2.0" );

        assertThat( repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                             TEST_PROJECT_VERSION ) ).isNotNull().isEmpty();

        assertThat( repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT,
                                             TEST_PROJECT_VERSION_2_0 ) ).isNotEmpty().hasSize( 1 );
    }

    @Test
    public void deleteArtifact()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        artifact.addFacet( new TestMetadataFacet( "value" ) );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        Collection<ArtifactMetadata> artifacts =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertEquals( Collections.singletonList( artifact ), new ArrayList<>( artifacts ) );

        repository.removeArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact.getId() );

        artifacts = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        assertThat( artifacts ).isNotNull().isEmpty();
    }

    @Test
    public void deleteVersion()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        artifact.addFacet( new TestMetadataFacet( "value" ) );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        Collection<String> versions = repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        assertThat( versions ).isNotNull().isNotEmpty().hasSize( 1 );

        repository.removeProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        versions = repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        assertThat( versions ).isNotNull().isEmpty();
    }

    @Test
    public void deleteProject()
        throws Exception
    {
        ArtifactMetadata artifact = createArtifact();
        artifact.addFacet( new TestMetadataFacet( "value" ) );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );

        assertEquals( 1, repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT ).size() );

        repository.removeProject( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        Collection<String> versions = repository.getProjectVersions( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT );

        assertThat( versions ).isNotNull().isEmpty();
    }


    @Test
    public void deleteSnapshotVersion()
        throws Exception
    {
        ArtifactMetadata artifactOne = createArtifact();
        artifactOne.setVersion( "2.0-20120618.214127-1" );
        artifactOne.setProjectVersion( "2.0-SNAPSHOT" );
        artifactOne.addFacet( new TestMetadataFacet( "value" ) );
        artifactOne.setId( TEST_PROJECT + "-" + "2.0-20120618.214127-1" + "." + "jar" );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, "2.0-SNAPSHOT", artifactOne );

        ArtifactMetadata artifactTwo = createArtifact();
        artifactTwo.setVersion( "2.0-20120618.214135-2" );
        artifactTwo.setProjectVersion( "2.0-SNAPSHOT" );
        artifactTwo.addFacet( new TestMetadataFacet( "value" ) );
        artifactTwo.setId( TEST_PROJECT + "-" + "2.0-20120618.214135-2" + "." + "jar" );

        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, "2.0-SNAPSHOT", artifactTwo );

        Collection<ArtifactMetadata> artifactMetadatas =
            repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, "2.0-SNAPSHOT" );

        assertThat( artifactMetadatas ).isNotNull().isNotEmpty().hasSize( 2 );

        log.info( "artifactMetadatas: {}", artifactMetadatas );

        repository.removeArtifact( artifactOne, "2.0-SNAPSHOT" );

        artifactMetadatas = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, "2.0-SNAPSHOT" );

        assertThat( artifactMetadatas ).isNotNull().isNotEmpty().hasSize( 1 );

        repository.removeArtifact( artifactTwo, "2.0-SNAPSHOT" );

        artifactMetadatas = repository.getArtifacts( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, "2.0-SNAPSHOT" );

        assertThat( artifactMetadatas ).isNotNull().isEmpty();
    }


    @Test
    public void testgetProjectReferences()
        throws Exception
    {
        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
        metadata.setId( TEST_PROJECT_VERSION );

        metadata.setName( "project name" );
        metadata.setDescription( "project description" );
        metadata.setUrl( "the url" );

        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" );
        d.setOptional( true );
        metadata.addDependency( d );

        d = new Dependency();
        d.setArtifactId( "artifactId1" );
        d.setClassifier( "classifier" );
        d.setGroupId( "groupId" );
        d.setScope( "scope" );
        d.setSystemPath( "system path" );
        d.setType( "type" );
        d.setVersion( "version1" );
        d.setOptional( true );
        metadata.addDependency( d );

        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );

        repository.save();

        metadata = repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );

        Collection<ProjectVersionReference> references =
            repository.getProjectReferences( TEST_REPO_ID, d.getGroupId(), d.getArtifactId(), d.getVersion() );

        log.info( "references: {}", references );

        assertThat( references ).isNotNull().hasSize( 1 ).contains(
            new ProjectVersionReference( ProjectVersionReference.ReferenceType.DEPENDENCY, TEST_PROJECT, TEST_NAMESPACE,
                                         TEST_PROJECT_VERSION ) );

    }

    @Test
    public void testSearchArtifactsByKey()
        throws Exception
    {
        createArtifactWithData();
        Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( "url", TEST_URL, TEST_REPO_ID, false );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
    }

    @Test
    public void testSearchArtifactsByKeyExact()
        throws Exception
    {
        createArtifactWithData();
        Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( "url", TEST_URL, TEST_REPO_ID, true );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
        artifactsByProperty = repository.searchArtifacts( "org.name", "pache", TEST_REPO_ID, true );
        assertThat( artifactsByProperty ).isNotNull().isEmpty();
    }

    @Test
    public void testSearchArtifactsByFacetKey()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( TEST_METADATA_KEY, TEST_METADATA_VALUE, TEST_REPO_ID, false );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
    }

    @Test
    public void testSearchArtifactsByFacetKeyAllRepos()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( TEST_METADATA_KEY, TEST_METADATA_VALUE, null, false );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
    }

    @Test
    public void testSearchArtifactsFullText()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        // only works in JCR
        // Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( TEST_URL, TEST_REPO_ID, false );
        Collection<ArtifactMetadata> artifactsByProperty =
            repository.searchArtifacts( TEST_METADATA_VALUE, TEST_REPO_ID, false );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
    }

    @Test
    public void testSearchArtifactsFullTextExact()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        // only works in JCR
        // Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( TEST_URL, TEST_REPO_ID, true );
        Collection<ArtifactMetadata> artifactsByProperty =
            repository.searchArtifacts( TEST_METADATA_VALUE, TEST_REPO_ID, true );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
        artifactsByProperty = repository.searchArtifacts( TEST_METADATA_VALUE.substring( 2 ), TEST_REPO_ID, true );
        assertThat( artifactsByProperty ).isNotNull().isEmpty();
    }

    @Test
    public void testSearchArtifactsFullTextByFacet()
        throws Exception
    {
        createArtifactWithGenericMetadataFacet();
        Collection<ArtifactMetadata> artifactsByProperty = repository.searchArtifacts( TEST_METADATA_VALUE, TEST_REPO_ID, false );
        assertThat( artifactsByProperty ).isNotNull().isNotEmpty();
    }

    private static ProjectMetadata createProject()
    {
        return createProject( TEST_NAMESPACE );
    }

    private static ProjectMetadata createProject( String ns )
    {
        ProjectMetadata project = new ProjectMetadata();
        project.setId( TEST_PROJECT );
        project.setNamespace( ns );
        return project;
    }

    private void createArtifactWithGenericMetadataFacet()
        throws MetadataRepositoryException, MetadataResolutionException
    {
        createArtifactWithGenericMetadataFacet( 1 );
    }

    private void createArtifactWithGenericMetadataFacet( int artifacts )
        throws MetadataRepositoryException, MetadataResolutionException
    {
        MetadataFacet metadataFacet = new GenericMetadataFacet();
        Map<String, String> properties = new HashMap<>();
        properties.put( TEST_METADATA_KEY, TEST_METADATA_VALUE );
        metadataFacet.fromProperties( properties );
        createArtifactWithFacet( artifacts, null, metadataFacet );
    }

    private void createArtifactWithMavenArtifactFacet()
        throws MetadataRepositoryException, MetadataResolutionException
    {
        createArtifactWithMavenArtifactFacet( 1 );
    }

    private void createArtifactWithMavenArtifactFacet( int artifacts )
        throws MetadataRepositoryException, MetadataResolutionException
    {
        TestMetadataFacet facet = new TestMetadataFacet( TEST_METADATA_VALUE );
        createArtifactWithFacet( artifacts, facet, null );
    }

    private void createArtifactWithFacet( int artifacts, MetadataFacet artifactFacet,
                                          MetadataFacet projectVersionMetadataFacet )
        throws MetadataRepositoryException, MetadataResolutionException
    {
        for ( int i = 0; i < artifacts; i++ )
        {
            ArtifactMetadata artifact = createArtifact();
            if ( artifactFacet != null )
            {
                artifact.addFacet( artifactFacet );
            }
            repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        }
        if ( projectVersionMetadataFacet != null )
        {
            ProjectVersionMetadata metadata =
                repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
            metadata.addFacet( projectVersionMetadataFacet );
            metadata.setOrganization( TEST_ORGANIZATION );
            metadata.setUrl( TEST_URL );
            repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );
        }
        repository.save();
    }

    private void createArtifactWithData()
        throws MetadataRepositoryException, MetadataResolutionException
    {
        ArtifactMetadata artifact = createArtifact();
        repository.updateArtifact( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION, artifact );
        ProjectVersionMetadata metadata =
            repository.getProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, TEST_PROJECT_VERSION );
        metadata.setOrganization( TEST_ORGANIZATION );
        metadata.setUrl( TEST_URL );

        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );
        repository.save();
    }

    private static ArtifactMetadata createArtifact()
    {
        return createArtifact( "jar" );
    }

    private static ArtifactMetadata createArtifact( String type )
    {
        ArtifactMetadata artifact = new ArtifactMetadata();
        artifact.setId( TEST_PROJECT + "-" + TEST_PROJECT_VERSION + "." + type );
        artifact.setWhenGathered( new Date() );
        artifact.setNamespace( TEST_NAMESPACE );
        artifact.setProject( TEST_PROJECT );
        artifact.setRepositoryId( TEST_REPO_ID );
        artifact.setFileLastModified( System.currentTimeMillis() );
        artifact.setVersion( TEST_PROJECT_VERSION );
        artifact.setProjectVersion( TEST_PROJECT_VERSION );
        artifact.setMd5( TEST_MD5 );
        artifact.setSha1( TEST_SHA1 );
        return artifact;
    }

    private static class ArtifactMetadataComparator
        implements Comparator<ArtifactMetadata>
    {
        @Override
        public final int compare( ArtifactMetadata a, ArtifactMetadata b )
        {
            return a.getProject().compareTo( b.getProject() );
        }
    }

    private static class KindOfRepositoryStatistics
        implements MetadataFacet
    {
        private String value;

        private Date date;

        static final String SCAN_TIMESTAMP_FORMAT = "yyyy/MM/dd/HHmmss.SSS";

        private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );

        private KindOfRepositoryStatistics( String value, Date date )
        {
            this.value = value;
            this.date = date;
        }

        @Override
        public String getName()
        {
            return createNameFormat().format( date );
        }

        private static SimpleDateFormat createNameFormat()
        {
            SimpleDateFormat fmt = new SimpleDateFormat( SCAN_TIMESTAMP_FORMAT );
            fmt.setTimeZone( UTC_TIME_ZONE );
            return fmt;
        }

        @Override
        public String getFacetId()
        {
            return KindOfRepositoryStatistics.class.getName();
        }

        @Override
        public Map<String, String> toProperties()
        {
            return Collections.emptyMap();
        }

        @Override
        public void fromProperties( Map<String, String> properties )
        {
            // no op
        }
    }

    private static class TestMetadataFacet
        implements MetadataFacet
    {
        private String testFacetId;

        private Map<String, String> additionalProps;

        private String value;

        private TestMetadataFacet( String value )
        {
            this.value = value;
            testFacetId = TEST_FACET_ID;
        }

        private TestMetadataFacet( String facetId, String value )
        {
            this.value = value;
            testFacetId = facetId;
        }

        private TestMetadataFacet( String facetId, String value, Map<String, String> additionalProps )
        {
            this( facetId, value );
            this.additionalProps = additionalProps;
        }

        @Override
        public String getFacetId()
        {
            return testFacetId;
        }

        @Override
        public String getName()
        {
            return TEST_NAME;
        }

        @Override
        public Map<String, String> toProperties()
        {
            if ( value != null )
            {
                if ( additionalProps == null )
                {
                    return Collections.singletonMap( "foo", value );
                }
                else
                {
                    Map<String, String> props = new HashMap<>();
                    props.put( "foo", value );

                    for ( String key : additionalProps.keySet() )
                    {
                        props.put( key, additionalProps.get( key ) );
                    }
                    return props;
                }
            }
            else
            {
                return Collections.emptyMap();
            }
        }

        @Override
        public void fromProperties( Map<String, String> properties )
        {
            String value = properties.get( "foo" );
            if ( value != null )
            {
                this.value = value;
            }

            properties.remove( "foo" );

            if ( additionalProps == null )
            {
                additionalProps = new HashMap<>();
            }

            for ( String key : properties.keySet() )
            {
                additionalProps.put( key, properties.get( key ) );
            }
        }

        public String getValue()
        {
            return value;
        }

        @Override
        public String toString()
        {
            return "TestMetadataFacet{" + "value='" + value + '\'' + '}';
        }

        @Override
        public boolean equals( Object o )
        {
            if ( this == o )
            {
                return true;
            }
            if ( o == null || getClass() != o.getClass() )
            {
                return false;
            }

            TestMetadataFacet that = (TestMetadataFacet) o;

            if ( value != null ? !value.equals( that.value ) : that.value != null )
            {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode()
        {
            return value != null ? value.hashCode() : 0;
        }
    }
}
