org.apache.archiva.metadata.repository.file.FileMetadataRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.archiva.metadata.repository.file.FileMetadataRepository.java

Source

package org.apache.archiva.metadata.repository.file;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.archiva.configuration.ArchivaConfiguration;
import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
import org.apache.archiva.metadata.model.ArtifactMetadata;
import org.apache.archiva.metadata.model.CiManagement;
import org.apache.archiva.metadata.model.Dependency;
import org.apache.archiva.metadata.model.IssueManagement;
import org.apache.archiva.metadata.model.License;
import org.apache.archiva.metadata.model.MailingList;
import org.apache.archiva.metadata.model.MetadataFacet;
import org.apache.archiva.metadata.model.MetadataFacetFactory;
import org.apache.archiva.metadata.model.Organization;
import org.apache.archiva.metadata.model.ProjectMetadata;
import org.apache.archiva.metadata.model.ProjectVersionMetadata;
import org.apache.archiva.metadata.model.ProjectVersionReference;
import org.apache.archiva.metadata.model.Scm;
import org.apache.archiva.metadata.repository.MetadataRepository;
import org.apache.archiva.metadata.repository.MetadataRepositoryException;
import org.apache.archiva.metadata.repository.MetadataResolutionException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

public class FileMetadataRepository implements MetadataRepository {
    private final Map<String, MetadataFacetFactory> metadataFacetFactories;

    private final ArchivaConfiguration configuration;

    private Logger log = LoggerFactory.getLogger(FileMetadataRepository.class);

    private static final String PROJECT_METADATA_KEY = "project-metadata";

    private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata";

    private static final String NAMESPACE_METADATA_KEY = "namespace-metadata";

    private static final String METADATA_KEY = "metadata";

    public FileMetadataRepository(Map<String, MetadataFacetFactory> metadataFacetFactories,
            ArchivaConfiguration configuration) {
        this.metadataFacetFactories = metadataFacetFactories;
        this.configuration = configuration;
    }

    private File getBaseDirectory(String repoId) throws IOException {
        // TODO: should be configurable, like the index
        ManagedRepositoryConfiguration managedRepositoryConfiguration = configuration.getConfiguration()
                .getManagedRepositoriesAsMap().get(repoId);
        if (managedRepositoryConfiguration == null) {
            return Files.createTempDirectory(repoId).toFile();
        }
        String basedir = managedRepositoryConfiguration.getLocation();
        return new File(basedir, ".archiva");
    }

    private File getDirectory(String repoId) throws IOException {
        return new File(getBaseDirectory(repoId), "content");
    }

    @Override
    public void updateProject(String repoId, ProjectMetadata project) {
        updateProject(repoId, project.getNamespace(), project.getId());
    }

    private void updateProject(String repoId, String namespace, String id) {
        // TODO: this is a more braindead implementation than we would normally expect, for prototyping purposes
        updateNamespace(repoId, namespace);

        try {
            File namespaceDirectory = new File(getDirectory(repoId), namespace);
            Properties properties = new Properties();
            properties.setProperty("namespace", namespace);
            properties.setProperty("id", id);
            writeProperties(properties, new File(namespaceDirectory, id), PROJECT_METADATA_KEY);
        } catch (IOException e) {
            // TODO!
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void updateProjectVersion(String repoId, String namespace, String projectId,
            ProjectVersionMetadata versionMetadata) {

        try {
            updateProject(repoId, namespace, projectId);

            File directory = new File(getDirectory(repoId),
                    namespace + "/" + projectId + "/" + versionMetadata.getId());

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
            // remove properties that are not references or artifacts
            for (Object key : new ArrayList(properties.keySet())) {
                String name = (String) key;
                if (!name.contains(":") && !name.equals("facetIds")) {
                    properties.remove(name);
                }

                // clear the facet contents so old properties are no longer written
                clearMetadataFacetProperties(versionMetadata.getFacetList(), properties, "");
            }
            properties.setProperty("id", versionMetadata.getId());
            setProperty(properties, "name", versionMetadata.getName());
            setProperty(properties, "description", versionMetadata.getDescription());
            setProperty(properties, "url", versionMetadata.getUrl());
            setProperty(properties, "incomplete", String.valueOf(versionMetadata.isIncomplete()));
            if (versionMetadata.getScm() != null) {
                setProperty(properties, "scm.connection", versionMetadata.getScm().getConnection());
                setProperty(properties, "scm.developerConnection",
                        versionMetadata.getScm().getDeveloperConnection());
                setProperty(properties, "scm.url", versionMetadata.getScm().getUrl());
            }
            if (versionMetadata.getCiManagement() != null) {
                setProperty(properties, "ci.system", versionMetadata.getCiManagement().getSystem());
                setProperty(properties, "ci.url", versionMetadata.getCiManagement().getUrl());
            }
            if (versionMetadata.getIssueManagement() != null) {
                setProperty(properties, "issue.system", versionMetadata.getIssueManagement().getSystem());
                setProperty(properties, "issue.url", versionMetadata.getIssueManagement().getUrl());
            }
            if (versionMetadata.getOrganization() != null) {
                setProperty(properties, "org.name", versionMetadata.getOrganization().getName());
                setProperty(properties, "org.url", versionMetadata.getOrganization().getUrl());
            }
            int i = 0;
            for (License license : versionMetadata.getLicenses()) {
                setProperty(properties, "license." + i + ".name", license.getName());
                setProperty(properties, "license." + i + ".url", license.getUrl());
                i++;
            }
            i = 0;
            for (MailingList mailingList : versionMetadata.getMailingLists()) {
                setProperty(properties, "mailingList." + i + ".archive", mailingList.getMainArchiveUrl());
                setProperty(properties, "mailingList." + i + ".name", mailingList.getName());
                setProperty(properties, "mailingList." + i + ".post", mailingList.getPostAddress());
                setProperty(properties, "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress());
                setProperty(properties, "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress());
                setProperty(properties, "mailingList." + i + ".otherArchives",
                        join(mailingList.getOtherArchives()));
                i++;
            }
            i = 0;
            ProjectVersionReference reference = new ProjectVersionReference();
            reference.setNamespace(namespace);
            reference.setProjectId(projectId);
            reference.setProjectVersion(versionMetadata.getId());
            reference.setReferenceType(ProjectVersionReference.ReferenceType.DEPENDENCY);
            for (Dependency dependency : versionMetadata.getDependencies()) {
                setProperty(properties, "dependency." + i + ".classifier", dependency.getClassifier());
                setProperty(properties, "dependency." + i + ".scope", dependency.getScope());
                setProperty(properties, "dependency." + i + ".systemPath", dependency.getSystemPath());
                setProperty(properties, "dependency." + i + ".artifactId", dependency.getArtifactId());
                setProperty(properties, "dependency." + i + ".groupId", dependency.getGroupId());
                setProperty(properties, "dependency." + i + ".version", dependency.getVersion());
                setProperty(properties, "dependency." + i + ".type", dependency.getType());
                setProperty(properties, "dependency." + i + ".optional", String.valueOf(dependency.isOptional()));

                updateProjectReference(repoId, dependency.getGroupId(), dependency.getArtifactId(),
                        dependency.getVersion(), reference);

                i++;
            }
            Set<String> facetIds = new LinkedHashSet<String>(versionMetadata.getFacetIds());
            facetIds.addAll(Arrays.asList(properties.getProperty("facetIds", "").split(",")));
            properties.setProperty("facetIds", join(facetIds));

            updateProjectVersionFacets(versionMetadata, properties);

            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
        } catch (IOException e) {
            // TODO
            log.error(e.getMessage(), e);
        }
    }

    private void updateProjectVersionFacets(ProjectVersionMetadata versionMetadata, Properties properties) {
        for (MetadataFacet facet : versionMetadata.getFacetList()) {
            for (Map.Entry<String, String> entry : facet.toProperties().entrySet()) {
                properties.setProperty(facet.getFacetId() + ":" + entry.getKey(), entry.getValue());
            }
        }
    }

    private static void clearMetadataFacetProperties(Collection<MetadataFacet> facetList, Properties properties,
            String prefix) {
        List<Object> propsToRemove = new ArrayList<>();
        for (MetadataFacet facet : facetList) {
            for (Object key : new ArrayList(properties.keySet())) {
                String keyString = (String) key;
                if (keyString.startsWith(prefix + facet.getFacetId() + ":")) {
                    propsToRemove.add(key);
                }
            }
        }

        for (Object key : propsToRemove) {
            properties.remove(key);
        }
    }

    private void updateProjectReference(String repoId, String namespace, String projectId, String projectVersion,
            ProjectVersionReference reference) {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
            int i = Integer.parseInt(properties.getProperty("ref:lastReferenceNum", "-1")) + 1;
            setProperty(properties, "ref:lastReferenceNum", Integer.toString(i));
            setProperty(properties, "ref:reference." + i + ".namespace", reference.getNamespace());
            setProperty(properties, "ref:reference." + i + ".projectId", reference.getProjectId());
            setProperty(properties, "ref:reference." + i + ".projectVersion", reference.getProjectVersion());
            setProperty(properties, "ref:reference." + i + ".referenceType",
                    reference.getReferenceType().toString());

            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
        } catch (IOException e) {
            // TODO
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void updateNamespace(String repoId, String namespace) {
        try {
            File namespaceDirectory = new File(getDirectory(repoId), namespace);
            Properties properties = new Properties();
            properties.setProperty("namespace", namespace);
            writeProperties(properties, namespaceDirectory, NAMESPACE_METADATA_KEY);

        } catch (IOException e) {
            // TODO!
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public List<String> getMetadataFacets(String repoId, String facetId) throws MetadataRepositoryException {
        try {
            File directory = getMetadataDirectory(repoId, facetId);
            List<String> facets = new ArrayList<>();
            recurse(facets, "", directory);
            return facets;
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public boolean hasMetadataFacet(String repositoryId, String facetId) throws MetadataRepositoryException {
        // TODO could be improved a bit
        return !getMetadataFacets(repositoryId, facetId).isEmpty();
    }

    private void recurse(List<String> facets, String prefix, File directory) {
        File[] list = directory.listFiles();
        if (list != null) {
            for (File dir : list) {
                if (dir.isDirectory()) {
                    recurse(facets, prefix + "/" + dir.getName(), dir);
                } else if (dir.getName().equals(METADATA_KEY + ".properties")) {
                    facets.add(prefix.substring(1));
                }
            }
        }
    }

    @Override
    public MetadataFacet getMetadataFacet(String repositoryId, String facetId, String name) {
        Properties properties;
        try {
            properties = readProperties(new File(getMetadataDirectory(repositoryId, facetId), name), METADATA_KEY);
        } catch (FileNotFoundException e) {
            return null;
        } catch (IOException e) {
            // TODO
            log.error(e.getMessage(), e);
            return null;
        }
        MetadataFacet metadataFacet = null;
        MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get(facetId);
        if (metadataFacetFactory != null) {
            metadataFacet = metadataFacetFactory.createMetadataFacet(repositoryId, name);
            Map<String, String> map = new HashMap<>();
            for (Object key : new ArrayList(properties.keySet())) {
                String property = (String) key;
                map.put(property, properties.getProperty(property));
            }
            metadataFacet.fromProperties(map);
        }
        return metadataFacet;
    }

    @Override
    public void addMetadataFacet(String repositoryId, MetadataFacet metadataFacet) {
        Properties properties = new Properties();
        properties.putAll(metadataFacet.toProperties());

        try {
            File directory = new File(getMetadataDirectory(repositoryId, metadataFacet.getFacetId()),
                    metadataFacet.getName());
            writeProperties(properties, directory, METADATA_KEY);
        } catch (IOException e) {
            // TODO!
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public void removeMetadataFacets(String repositoryId, String facetId) throws MetadataRepositoryException {
        try {
            File dir = getMetadataDirectory(repositoryId, facetId);
            FileUtils.deleteDirectory(dir);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public void removeMetadataFacet(String repoId, String facetId, String name) throws MetadataRepositoryException {
        try {
            File dir = new File(getMetadataDirectory(repoId, facetId), name);
            FileUtils.deleteDirectory(dir);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public List<ArtifactMetadata> getArtifactsByDateRange(String repoId, Date startTime, Date endTime)
            throws MetadataRepositoryException {
        try {
            // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
            //  of this information (eg. in Lucene, as before)

            List<ArtifactMetadata> artifacts = new ArrayList<>();
            for (String ns : getRootNamespaces(repoId)) {
                getArtifactsByDateRange(artifacts, repoId, ns, startTime, endTime);
            }
            Collections.sort(artifacts, new ArtifactComparator());
            return artifacts;
        } catch (MetadataResolutionException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    private void getArtifactsByDateRange(List<ArtifactMetadata> artifacts, String repoId, String ns, Date startTime,
            Date endTime) throws MetadataRepositoryException {
        try {
            for (String namespace : getNamespaces(repoId, ns)) {
                getArtifactsByDateRange(artifacts, repoId, ns + "." + namespace, startTime, endTime);
            }

            for (String project : getProjects(repoId, ns)) {
                for (String version : getProjectVersions(repoId, ns, project)) {
                    for (ArtifactMetadata artifact : getArtifacts(repoId, ns, project, version)) {
                        if (startTime == null || startTime.before(artifact.getWhenGathered())) {
                            if (endTime == null || endTime.after(artifact.getWhenGathered())) {
                                artifacts.add(artifact);
                            }
                        }
                    }
                }
            }
        } catch (MetadataResolutionException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<ArtifactMetadata> getArtifacts(String repoId, String namespace, String projectId,
            String projectVersion) throws MetadataResolutionException {
        try {
            Map<String, ArtifactMetadata> artifacts = new HashMap<>();

            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);

            for (Map.Entry entry : properties.entrySet()) {
                String name = (String) entry.getKey();
                StringTokenizer tok = new StringTokenizer(name, ":");
                if (tok.hasMoreTokens() && "artifact".equals(tok.nextToken())) {
                    String field = tok.nextToken();
                    String id = tok.nextToken();

                    ArtifactMetadata artifact = artifacts.get(id);
                    if (artifact == null) {
                        artifact = new ArtifactMetadata();
                        artifact.setRepositoryId(repoId);
                        artifact.setNamespace(namespace);
                        artifact.setProject(projectId);
                        artifact.setProjectVersion(projectVersion);
                        artifact.setVersion(projectVersion);
                        artifact.setId(id);
                        artifacts.put(id, artifact);
                    }

                    String value = (String) entry.getValue();
                    if ("updated".equals(field)) {
                        artifact.setFileLastModified(Long.parseLong(value));
                    } else if ("size".equals(field)) {
                        artifact.setSize(Long.valueOf(value));
                    } else if ("whenGathered".equals(field)) {
                        artifact.setWhenGathered(new Date(Long.parseLong(value)));
                    } else if ("version".equals(field)) {
                        artifact.setVersion(value);
                    } else if ("md5".equals(field)) {
                        artifact.setMd5(value);
                    } else if ("sha1".equals(field)) {
                        artifact.setSha1(value);
                    } else if ("facetIds".equals(field)) {
                        if (value.length() > 0) {
                            String propertyPrefix = "artifact:facet:" + id + ":";
                            for (String facetId : value.split(",")) {
                                MetadataFacetFactory factory = metadataFacetFactories.get(facetId);
                                if (factory == null) {
                                    log.error("Attempted to load unknown artifact metadata facet: " + facetId);
                                } else {
                                    MetadataFacet facet = factory.createMetadataFacet();
                                    String prefix = propertyPrefix + facet.getFacetId();
                                    Map<String, String> map = new HashMap<>();
                                    for (Object key : new ArrayList(properties.keySet())) {
                                        String property = (String) key;
                                        if (property.startsWith(prefix)) {
                                            map.put(property.substring(prefix.length() + 1),
                                                    properties.getProperty(property));
                                        }
                                    }
                                    facet.fromProperties(map);
                                    artifact.addFacet(facet);
                                }
                            }
                        }

                        updateArtifactFacets(artifact, properties);
                    }
                }
            }
            return artifacts.values();
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public void save() {
        // it's all instantly persisted
    }

    @Override
    public void close() {
        // nothing additional to close
    }

    @Override
    public void revert() {
        log.warn("Attempted to revert a session, but the file-based repository storage doesn't support it");
    }

    @Override
    public boolean canObtainAccess(Class<?> aClass) {
        return false;
    }

    @Override
    public <T> T obtainAccess(Class<T> aClass) {
        throw new IllegalArgumentException(
                "Access using " + aClass + " is not supported on the file metadata storage");
    }

    private void updateArtifactFacets(ArtifactMetadata artifact, Properties properties) {
        String propertyPrefix = "artifact:facet:" + artifact.getId() + ":";
        for (MetadataFacet facet : artifact.getFacetList()) {
            for (Map.Entry<String, String> e : facet.toProperties().entrySet()) {
                String key = propertyPrefix + facet.getFacetId() + ":" + e.getKey();
                properties.setProperty(key, e.getValue());
            }
        }
    }

    @Override
    public Collection<String> getRepositories() {
        List<String> repositories = new ArrayList<>();
        for (ManagedRepositoryConfiguration managedRepositoryConfiguration : configuration.getConfiguration()
                .getManagedRepositories()) {
            repositories.add(managedRepositoryConfiguration.getId());
        }
        return repositories;
    }

    @Override
    public List<ArtifactMetadata> getArtifactsByChecksum(String repositoryId, String checksum)
            throws MetadataRepositoryException {
        try {
            // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
            //  of this information (eg. in Lucene, as before)
            // alternatively, we could build a referential tree in the content repository, however it would need some levels
            // of depth to avoid being too broad to be useful (eg. /repository/checksums/a/ab/abcdef1234567)

            List<ArtifactMetadata> artifacts = new ArrayList<>();
            for (String ns : getRootNamespaces(repositoryId)) {
                getArtifactsByChecksum(artifacts, repositoryId, ns, checksum);
            }
            return artifacts;
        } catch (MetadataResolutionException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public void removeNamespace(String repositoryId, String project) throws MetadataRepositoryException {
        try {
            File namespaceDirectory = new File(getDirectory(repositoryId), project);
            FileUtils.deleteDirectory(namespaceDirectory);
            //Properties properties = new Properties();
            //properties.setProperty( "namespace", namespace );
            //writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY );

        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public void removeArtifact(ArtifactMetadata artifactMetadata, String baseVersion)
            throws MetadataRepositoryException {

        try {
            File directory = new File(getDirectory(artifactMetadata.getRepositoryId()),
                    artifactMetadata.getNamespace() + "/" + artifactMetadata.getProject() + "/" + baseVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);

            String id = artifactMetadata.getId();

            properties.remove("artifact:updated:" + id);
            properties.remove("artifact:whenGathered:" + id);
            properties.remove("artifact:size:" + id);
            properties.remove("artifact:md5:" + id);
            properties.remove("artifact:sha1:" + id);
            properties.remove("artifact:version:" + id);
            properties.remove("artifact:facetIds:" + id);

            String prefix = "artifact:facet:" + id + ":";
            for (Object key : new ArrayList(properties.keySet())) {
                String property = (String) key;
                if (property.startsWith(prefix)) {
                    properties.remove(property);
                }
            }

            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }

    }

    @Override
    public void removeArtifact(String repoId, String namespace, String project, String version, String id)
            throws MetadataRepositoryException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + project + "/" + version);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);

            properties.remove("artifact:updated:" + id);
            properties.remove("artifact:whenGathered:" + id);
            properties.remove("artifact:size:" + id);
            properties.remove("artifact:md5:" + id);
            properties.remove("artifact:sha1:" + id);
            properties.remove("artifact:version:" + id);
            properties.remove("artifact:facetIds:" + id);

            String prefix = "artifact:facet:" + id + ":";
            for (Object key : new ArrayList(properties.keySet())) {
                String property = (String) key;
                if (property.startsWith(prefix)) {
                    properties.remove(property);
                }
            }

            FileUtils.deleteDirectory(directory);
            //writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    /**
     * FIXME implements this !!!!
     *
     * @param repositoryId
     * @param namespace
     * @param project
     * @param projectVersion
     * @param metadataFacet  will remove artifacts which have this {@link MetadataFacet} using equals
     * @throws MetadataRepositoryException
     */
    @Override
    public void removeArtifact(String repositoryId, String namespace, String project, String projectVersion,
            MetadataFacet metadataFacet) throws MetadataRepositoryException {
        throw new UnsupportedOperationException("not implemented");
    }

    @Override
    public void removeRepository(String repoId) throws MetadataRepositoryException {
        try {
            File dir = getDirectory(repoId);
            FileUtils.deleteDirectory(dir);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    private void getArtifactsByChecksum(List<ArtifactMetadata> artifacts, String repositoryId, String ns,
            String checksum) throws MetadataRepositoryException {
        try {
            for (String namespace : getNamespaces(repositoryId, ns)) {
                getArtifactsByChecksum(artifacts, repositoryId, ns + "." + namespace, checksum);
            }

            for (String project : getProjects(repositoryId, ns)) {
                for (String version : getProjectVersions(repositoryId, ns, project)) {
                    for (ArtifactMetadata artifact : getArtifacts(repositoryId, ns, project, version)) {
                        if (checksum.equals(artifact.getMd5()) || checksum.equals(artifact.getSha1())) {
                            artifacts.add(artifact);
                        }
                    }
                }
            }
        } catch (MetadataResolutionException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata(String key, String value,
            String repositoryId) throws MetadataRepositoryException {
        throw new UnsupportedOperationException("not yet implemented in File backend");
    }

    @Override
    public List<ArtifactMetadata> getArtifactsByMetadata(String key, String value, String repositoryId)
            throws MetadataRepositoryException {
        throw new UnsupportedOperationException("not yet implemented in File backend");
    }

    @Override
    public List<ArtifactMetadata> getArtifactsByProperty(String key, String value, String repositoryId)
            throws MetadataRepositoryException {
        throw new UnsupportedOperationException("getArtifactsByProperty not yet implemented in File backend");
    }

    private File getMetadataDirectory(String repoId, String facetId) throws IOException {
        return new File(getBaseDirectory(repoId), "facets/" + facetId);
    }

    private String join(Collection<String> ids) {
        if (ids != null && !ids.isEmpty()) {
            StringBuilder s = new StringBuilder();
            for (String id : ids) {
                s.append(id);
                s.append(",");
            }
            return s.substring(0, s.length() - 1);
        }
        return "";
    }

    private void setProperty(Properties properties, String name, String value) {
        if (value != null) {
            properties.setProperty(name, value);
        }
    }

    @Override
    public void updateArtifact(String repoId, String namespace, String projectId, String projectVersion,
            ArtifactMetadata artifact) {
        try {
            ProjectVersionMetadata metadata = new ProjectVersionMetadata();
            metadata.setId(projectVersion);
            updateProjectVersion(repoId, namespace, projectId, metadata);

            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);

            clearMetadataFacetProperties(artifact.getFacetList(), properties,
                    "artifact:facet:" + artifact.getId() + ":");

            String id = artifact.getId();
            properties.setProperty("artifact:updated:" + id,
                    Long.toString(artifact.getFileLastModified().getTime()));
            properties.setProperty("artifact:whenGathered:" + id,
                    Long.toString(artifact.getWhenGathered().getTime()));
            properties.setProperty("artifact:size:" + id, Long.toString(artifact.getSize()));
            if (artifact.getMd5() != null) {
                properties.setProperty("artifact:md5:" + id, artifact.getMd5());
            }
            if (artifact.getSha1() != null) {
                properties.setProperty("artifact:sha1:" + id, artifact.getSha1());
            }
            properties.setProperty("artifact:version:" + id, artifact.getVersion());

            Set<String> facetIds = new LinkedHashSet<String>(artifact.getFacetIds());
            String property = "artifact:facetIds:" + id;
            facetIds.addAll(Arrays.asList(properties.getProperty(property, "").split(",")));
            properties.setProperty(property, join(facetIds));

            updateArtifactFacets(artifact, properties);

            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
        } catch (IOException e) {
            // TODO
            log.error(e.getMessage(), e);
        }
    }

    private Properties readOrCreateProperties(File directory, String propertiesKey) {
        try {
            return readProperties(directory, propertiesKey);
        } catch (FileNotFoundException | NoSuchFileException e) {
            // ignore and return new properties
        } catch (IOException e) {
            // TODO
            log.error(e.getMessage(), e);
        }
        return new Properties();
    }

    private Properties readProperties(File directory, String propertiesKey) throws IOException {
        Properties properties = new Properties();
        try (InputStream in = Files.newInputStream(new File(directory, propertiesKey + ".properties").toPath())) {

            properties.load(in);
        }
        return properties;
    }

    @Override
    public ProjectMetadata getProject(String repoId, String namespace, String projectId)
            throws MetadataResolutionException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId);

            Properties properties = readOrCreateProperties(directory, PROJECT_METADATA_KEY);

            ProjectMetadata project = null;

            String id = properties.getProperty("id");
            if (id != null) {
                project = new ProjectMetadata();
                project.setNamespace(properties.getProperty("namespace"));
                project.setId(id);
            }

            return project;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public ProjectVersionMetadata getProjectVersion(String repoId, String namespace, String projectId,
            String projectVersion) throws MetadataResolutionException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
            String id = properties.getProperty("id");
            ProjectVersionMetadata versionMetadata = null;
            if (id != null) {
                versionMetadata = new ProjectVersionMetadata();
                versionMetadata.setId(id);
                versionMetadata.setName(properties.getProperty("name"));
                versionMetadata.setDescription(properties.getProperty("description"));
                versionMetadata.setUrl(properties.getProperty("url"));
                versionMetadata.setIncomplete(Boolean.valueOf(properties.getProperty("incomplete", "false")));

                String scmConnection = properties.getProperty("scm.connection");
                String scmDeveloperConnection = properties.getProperty("scm.developerConnection");
                String scmUrl = properties.getProperty("scm.url");
                if (scmConnection != null || scmDeveloperConnection != null || scmUrl != null) {
                    Scm scm = new Scm();
                    scm.setConnection(scmConnection);
                    scm.setDeveloperConnection(scmDeveloperConnection);
                    scm.setUrl(scmUrl);
                    versionMetadata.setScm(scm);
                }

                String ciSystem = properties.getProperty("ci.system");
                String ciUrl = properties.getProperty("ci.url");
                if (ciSystem != null || ciUrl != null) {
                    CiManagement ci = new CiManagement();
                    ci.setSystem(ciSystem);
                    ci.setUrl(ciUrl);
                    versionMetadata.setCiManagement(ci);
                }

                String issueSystem = properties.getProperty("issue.system");
                String issueUrl = properties.getProperty("issue.url");
                if (issueSystem != null || issueUrl != null) {
                    IssueManagement issueManagement = new IssueManagement();
                    issueManagement.setSystem(issueSystem);
                    issueManagement.setUrl(issueUrl);
                    versionMetadata.setIssueManagement(issueManagement);
                }

                String orgName = properties.getProperty("org.name");
                String orgUrl = properties.getProperty("org.url");
                if (orgName != null || orgUrl != null) {
                    Organization org = new Organization();
                    org.setName(orgName);
                    org.setUrl(orgUrl);
                    versionMetadata.setOrganization(org);
                }

                boolean done = false;
                int i = 0;
                while (!done) {
                    String licenseName = properties.getProperty("license." + i + ".name");
                    String licenseUrl = properties.getProperty("license." + i + ".url");
                    if (licenseName != null || licenseUrl != null) {
                        License license = new License();
                        license.setName(licenseName);
                        license.setUrl(licenseUrl);
                        versionMetadata.addLicense(license);
                    } else {
                        done = true;
                    }
                    i++;
                }

                done = false;
                i = 0;
                while (!done) {
                    String mailingListName = properties.getProperty("mailingList." + i + ".name");
                    if (mailingListName != null) {
                        MailingList mailingList = new MailingList();
                        mailingList.setName(mailingListName);
                        mailingList.setMainArchiveUrl(properties.getProperty("mailingList." + i + ".archive"));
                        String p = properties.getProperty("mailingList." + i + ".otherArchives");
                        if (p != null && p.length() > 0) {
                            mailingList.setOtherArchives(Arrays.asList(p.split(",")));
                        } else {
                            mailingList.setOtherArchives(Collections.<String>emptyList());
                        }
                        mailingList.setPostAddress(properties.getProperty("mailingList." + i + ".post"));
                        mailingList.setSubscribeAddress(properties.getProperty("mailingList." + i + ".subscribe"));
                        mailingList
                                .setUnsubscribeAddress(properties.getProperty("mailingList." + i + ".unsubscribe"));
                        versionMetadata.addMailingList(mailingList);
                    } else {
                        done = true;
                    }
                    i++;
                }

                done = false;
                i = 0;
                while (!done) {
                    String dependencyArtifactId = properties.getProperty("dependency." + i + ".artifactId");
                    if (dependencyArtifactId != null) {
                        Dependency dependency = new Dependency();
                        dependency.setArtifactId(dependencyArtifactId);
                        dependency.setGroupId(properties.getProperty("dependency." + i + ".groupId"));
                        dependency.setClassifier(properties.getProperty("dependency." + i + ".classifier"));
                        dependency.setOptional(
                                Boolean.valueOf(properties.getProperty("dependency." + i + ".optional")));
                        dependency.setScope(properties.getProperty("dependency." + i + ".scope"));
                        dependency.setSystemPath(properties.getProperty("dependency." + i + ".systemPath"));
                        dependency.setType(properties.getProperty("dependency." + i + ".type"));
                        dependency.setVersion(properties.getProperty("dependency." + i + ".version"));
                        dependency.setOptional(
                                Boolean.valueOf(properties.getProperty("dependency." + i + ".optional")));
                        versionMetadata.addDependency(dependency);
                    } else {
                        done = true;
                    }
                    i++;
                }

                String facetIds = properties.getProperty("facetIds", "");
                if (facetIds.length() > 0) {
                    for (String facetId : facetIds.split(",")) {
                        MetadataFacetFactory factory = metadataFacetFactories.get(facetId);
                        if (factory == null) {
                            log.error("Attempted to load unknown project version metadata facet: {}", facetId);
                        } else {
                            MetadataFacet facet = factory.createMetadataFacet();
                            Map<String, String> map = new HashMap<>();
                            for (Object key : new ArrayList(properties.keySet())) {
                                String property = (String) key;
                                if (property.startsWith(facet.getFacetId())) {
                                    map.put(property.substring(facet.getFacetId().length() + 1),
                                            properties.getProperty(property));
                                }
                            }
                            facet.fromProperties(map);
                            versionMetadata.addFacet(facet);
                        }
                    }
                }

                updateProjectVersionFacets(versionMetadata, properties);
            }
            return versionMetadata;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<String> getArtifactVersions(String repoId, String namespace, String projectId,
            String projectVersion) throws MetadataResolutionException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);

            Set<String> versions = new HashSet<String>();
            for (Map.Entry entry : properties.entrySet()) {
                String name = (String) entry.getKey();
                if (name.startsWith("artifact:version:")) {
                    versions.add((String) entry.getValue());
                }
            }
            return versions;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<ProjectVersionReference> getProjectReferences(String repoId, String namespace,
            String projectId, String projectVersion) throws MetadataResolutionException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);

            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
            int numberOfRefs = Integer.parseInt(properties.getProperty("ref:lastReferenceNum", "-1")) + 1;

            List<ProjectVersionReference> references = new ArrayList<>();
            for (int i = 0; i < numberOfRefs; i++) {
                ProjectVersionReference reference = new ProjectVersionReference();
                reference.setProjectId(properties.getProperty("ref:reference." + i + ".projectId"));
                reference.setNamespace(properties.getProperty("ref:reference." + i + ".namespace"));
                reference.setProjectVersion(properties.getProperty("ref:reference." + i + ".projectVersion"));
                reference.setReferenceType(ProjectVersionReference.ReferenceType
                        .valueOf(properties.getProperty("ref:reference." + i + ".referenceType")));
                references.add(reference);
            }
            return references;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<String> getRootNamespaces(String repoId) throws MetadataResolutionException {
        return getNamespaces(repoId, null);
    }

    @Override
    public Collection<String> getNamespaces(String repoId, String baseNamespace)
            throws MetadataResolutionException {
        try {
            List<String> allNamespaces = new ArrayList<>();
            File directory = getDirectory(repoId);
            File[] files = directory.listFiles();
            if (files != null) {
                for (File namespace : files) {
                    if (new File(namespace, NAMESPACE_METADATA_KEY + ".properties").exists()) {
                        allNamespaces.add(namespace.getName());
                    }
                }
            }

            Set<String> namespaces = new LinkedHashSet<>();
            int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
            for (String namespace : allNamespaces) {
                if (baseNamespace == null || namespace.startsWith(baseNamespace + ".")) {
                    int i = namespace.indexOf('.', fromIndex);
                    if (i >= 0) {
                        namespaces.add(namespace.substring(fromIndex, i));
                    } else {
                        namespaces.add(namespace.substring(fromIndex));
                    }
                }
            }
            return new ArrayList<>(namespaces);
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<String> getProjects(String repoId, String namespace) throws MetadataResolutionException {
        try {
            List<String> projects = new ArrayList<>();
            File directory = new File(getDirectory(repoId), namespace);
            File[] files = directory.listFiles();
            if (files != null) {
                for (File project : files) {
                    if (new File(project, PROJECT_METADATA_KEY + ".properties").exists()) {
                        projects.add(project.getName());
                    }
                }
            }
            return projects;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public Collection<String> getProjectVersions(String repoId, String namespace, String projectId)
            throws MetadataResolutionException {
        try {
            List<String> projectVersions = new ArrayList<>();
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId);
            File[] files = directory.listFiles();
            if (files != null) {
                for (File projectVersion : files) {
                    if (new File(projectVersion, PROJECT_VERSION_METADATA_KEY + ".properties").exists()) {
                        projectVersions.add(projectVersion.getName());
                    }
                }
            }
            return projectVersions;
        } catch (IOException e) {
            throw new MetadataResolutionException(e.getMessage(), e);
        }
    }

    @Override
    public void removeProject(String repositoryId, String namespace, String projectId)
            throws MetadataRepositoryException {
        try {
            File directory = new File(getDirectory(repositoryId), namespace + "/" + projectId);
            FileUtils.deleteDirectory(directory);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    @Override
    public void removeProjectVersion(String repoId, String namespace, String projectId, String projectVersion)
            throws MetadataRepositoryException {
        try {
            File directory = new File(getDirectory(repoId), namespace + "/" + projectId + "/" + projectVersion);
            FileUtils.deleteDirectory(directory);
        } catch (IOException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }

    }

    private void writeProperties(Properties properties, File directory, String propertiesKey) throws IOException {
        directory.mkdirs();
        try (OutputStream os = Files.newOutputStream(new File(directory, propertiesKey + ".properties").toPath())) {
            properties.store(os, null);
        }
    }

    private static class ArtifactComparator implements Comparator<ArtifactMetadata> {
        @Override
        public int compare(ArtifactMetadata artifact1, ArtifactMetadata artifact2) {
            if (artifact1.getWhenGathered() == artifact2.getWhenGathered()) {
                return 0;
            }
            if (artifact1.getWhenGathered() == null) {
                return 1;
            }
            if (artifact2.getWhenGathered() == null) {
                return -1;
            }
            return artifact1.getWhenGathered().compareTo(artifact2.getWhenGathered());
        }
    }

    @Override
    public List<ArtifactMetadata> getArtifacts(String repoId) throws MetadataRepositoryException {
        try {
            List<ArtifactMetadata> artifacts = new ArrayList<>();
            for (String ns : getRootNamespaces(repoId)) {
                getArtifacts(artifacts, repoId, ns);
            }
            return artifacts;
        } catch (MetadataResolutionException e) {
            throw new MetadataRepositoryException(e.getMessage(), e);
        }
    }

    private void getArtifacts(List<ArtifactMetadata> artifacts, String repoId, String ns)
            throws MetadataResolutionException {
        for (String namespace : getNamespaces(repoId, ns)) {
            getArtifacts(artifacts, repoId, ns + "." + namespace);
        }

        for (String project : getProjects(repoId, ns)) {
            for (String version : getProjectVersions(repoId, ns, project)) {
                for (ArtifactMetadata artifact : getArtifacts(repoId, ns, project, version)) {
                    artifacts.add(artifact);
                }
            }
        }
    }

    @Override
    public List<ArtifactMetadata> searchArtifacts(String text, String repositoryId, boolean exact)
            throws MetadataRepositoryException {
        throw new UnsupportedOperationException("searchArtifacts not yet implemented in File backend");
    }

    @Override
    public List<ArtifactMetadata> searchArtifacts(String key, String text, String repositoryId, boolean exact)
            throws MetadataRepositoryException {
        throw new UnsupportedOperationException("searchArtifacts not yet implemented in File backend");
    }
}