This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.metadata.repository.file;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.checksum.ChecksumAlgorithm;
023import org.apache.archiva.configuration.ArchivaConfiguration;
024import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
025import org.apache.archiva.metadata.QueryParameter;
026import org.apache.archiva.metadata.model.ArtifactMetadata;
027import org.apache.archiva.metadata.model.CiManagement;
028import org.apache.archiva.metadata.model.Dependency;
029import org.apache.archiva.metadata.model.IssueManagement;
030import org.apache.archiva.metadata.model.License;
031import org.apache.archiva.metadata.model.MailingList;
032import org.apache.archiva.metadata.model.MetadataFacet;
033import org.apache.archiva.metadata.model.MetadataFacetFactory;
034import org.apache.archiva.metadata.model.Organization;
035import org.apache.archiva.metadata.model.ProjectMetadata;
036import org.apache.archiva.metadata.model.ProjectVersionMetadata;
037import org.apache.archiva.metadata.model.ProjectVersionReference;
038import org.apache.archiva.metadata.model.Scm;
039import org.apache.archiva.metadata.repository.AbstractMetadataRepository;
040import org.apache.archiva.metadata.repository.MetadataRepository;
041import org.apache.archiva.metadata.repository.MetadataRepositoryException;
042import org.apache.archiva.metadata.repository.MetadataResolutionException;
043import org.apache.archiva.metadata.repository.MetadataService;
044import org.apache.archiva.metadata.repository.RepositorySession;
045import org.apache.commons.lang3.StringUtils;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import javax.annotation.ParametersAreNonnullByDefault;
050import java.io.FileNotFoundException;
051import java.io.IOException;
052import java.io.InputStream;
053import java.io.OutputStream;
054import java.nio.file.FileVisitOption;
055import java.nio.file.Files;
056import java.nio.file.NoSuchFileException;
057import java.nio.file.Path;
058import java.nio.file.Paths;
059import java.time.Instant;
060import java.time.ZoneId;
061import java.time.ZonedDateTime;
062import java.util.ArrayList;
063import java.util.Arrays;
064import java.util.Collection;
065import java.util.Collections;
066import java.util.Comparator;
067import java.util.HashMap;
068import java.util.HashSet;
069import java.util.LinkedHashSet;
070import java.util.List;
071import java.util.Map;
072import java.util.Objects;
073import java.util.Properties;
074import java.util.Set;
075import java.util.StringTokenizer;
076import java.util.stream.Collectors;
077import java.util.stream.Stream;
078
079/**
080 * File implementation of the metadata repository. It uses property files in a separate directory tree.
081 * The implementation has no fulltext index. So fulltext queries are not supported.
082 *
083 * Some retrieval methods may not be very efficient.
084 */
085@ParametersAreNonnullByDefault
086public class FileMetadataRepository
087        extends AbstractMetadataRepository implements MetadataRepository {
088
089    private final ArchivaConfiguration configuration;
090
091    private Logger log = LoggerFactory.getLogger(FileMetadataRepository.class);
092
093    private static final String PROJECT_METADATA_KEY = "project-metadata";
094
095    private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata";
096
097    private static final String NAMESPACE_METADATA_KEY = "namespace-metadata";
098
099    private static final String METADATA_KEY = "metadata";
100
101    private Map<String, Path> baseDirectory = new HashMap<>();
102
103    public FileMetadataRepository(MetadataService metadataService,
104                                  ArchivaConfiguration configuration) {
105        super(metadataService);
106        this.configuration = configuration;
107    }
108
109    private Path getBaseDirectory(String repoId)
110            throws IOException {
111        if (!baseDirectory.containsKey(repoId)) {
112            Path baseDir;
113            ManagedRepositoryConfiguration managedRepositoryConfiguration =
114                    configuration.getConfiguration().getManagedRepositoriesAsMap().get(repoId);
115            if (managedRepositoryConfiguration == null) {
116                baseDir = Files.createTempDirectory(repoId);
117            } else {
118                baseDir = Paths.get(managedRepositoryConfiguration.getLocation());
119            }
120            baseDirectory.put(repoId, baseDir.resolve(".archiva"));
121        }
122        return baseDirectory.get(repoId);
123    }
124
125    private Path getDirectory(String repoId)
126            throws IOException {
127        return getBaseDirectory(repoId).resolve("content");
128    }
129
130    @Override
131    public void updateProject(RepositorySession session, String repoId, ProjectMetadata project) {
132        updateProject(session, repoId, project.getNamespace(), project.getId());
133    }
134
135    private void updateProject(RepositorySession session, String repoId, String namespace, String id) {
136        // TODO: this is a more braindead implementation than we would normally expect, for prototyping purposes
137        updateNamespace(session, repoId, namespace);
138
139        try {
140            Path namespaceDirectory = getDirectory(repoId).resolve(namespace);
141            Properties properties = new Properties();
142            properties.setProperty("namespace", namespace);
143            properties.setProperty("id", id);
144            writeProperties(properties, namespaceDirectory.resolve(id), PROJECT_METADATA_KEY);
145        } catch (IOException e) {
146            log.error("Could not update project {}, {}, {}: {}", repoId, namespace, id, e.getMessage(), e);
147        }
148    }
149
150    @Override
151    public void updateProjectVersion(RepositorySession session, String repoId, String namespace, String projectId,
152                                     ProjectVersionMetadata versionMetadata) {
153
154        try {
155            updateProject(session, repoId, namespace, projectId);
156
157            Path directory =
158                    getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + versionMetadata.getId());
159
160            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
161            // remove properties that are not references or artifacts
162            for (Object key : new ArrayList<>(properties.keySet())) {
163                String name = (String) key;
164                if (!name.contains(":") && !name.equals("facetIds")) {
165                    properties.remove(name);
166                }
167
168                // clear the facet contents so old properties are no longer written
169                clearMetadataFacetProperties(versionMetadata.getFacetList(), properties, "");
170            }
171            properties.setProperty("id", versionMetadata.getId());
172            setProperty(properties, "name", versionMetadata.getName());
173            setProperty(properties, "description", versionMetadata.getDescription());
174            setProperty(properties, "url", versionMetadata.getUrl());
175            setProperty(properties, "incomplete", String.valueOf(versionMetadata.isIncomplete()));
176            if (versionMetadata.getScm() != null) {
177                setProperty(properties, "scm.connection", versionMetadata.getScm().getConnection());
178                setProperty(properties, "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection());
179                setProperty(properties, "scm.url", versionMetadata.getScm().getUrl());
180            }
181            if (versionMetadata.getCiManagement() != null) {
182                setProperty(properties, "ci.system", versionMetadata.getCiManagement().getSystem());
183                setProperty(properties, "ci.url", versionMetadata.getCiManagement().getUrl());
184            }
185            if (versionMetadata.getIssueManagement() != null) {
186                setProperty(properties, "issue.system", versionMetadata.getIssueManagement().getSystem());
187                setProperty(properties, "issue.url", versionMetadata.getIssueManagement().getUrl());
188            }
189            if (versionMetadata.getOrganization() != null) {
190                setProperty(properties, "org.name", versionMetadata.getOrganization().getName());
191                setProperty(properties, "org.url", versionMetadata.getOrganization().getUrl());
192            }
193            int i = 0;
194            for (License license : versionMetadata.getLicenses()) {
195                setProperty(properties, "license." + i + ".name", license.getName());
196                setProperty(properties, "license." + i + ".url", license.getUrl());
197                i++;
198            }
199            i = 0;
200            for (MailingList mailingList : versionMetadata.getMailingLists()) {
201                setProperty(properties, "mailingList." + i + ".archive", mailingList.getMainArchiveUrl());
202                setProperty(properties, "mailingList." + i + ".name", mailingList.getName());
203                setProperty(properties, "mailingList." + i + ".post", mailingList.getPostAddress());
204                setProperty(properties, "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress());
205                setProperty(properties, "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress());
206                setProperty(properties, "mailingList." + i + ".otherArchives",
207                        join(mailingList.getOtherArchives()));
208                i++;
209            }
210            i = 0;
211            ProjectVersionReference reference = new ProjectVersionReference();
212            reference.setNamespace(namespace);
213            reference.setProjectId(projectId);
214            reference.setProjectVersion(versionMetadata.getId());
215            reference.setReferenceType(ProjectVersionReference.ReferenceType.DEPENDENCY);
216            for (Dependency dependency : versionMetadata.getDependencies()) {
217                setProperty(properties, "dependency." + i + ".classifier", dependency.getClassifier());
218                setProperty(properties, "dependency." + i + ".scope", dependency.getScope());
219                setProperty(properties, "dependency." + i + ".systemPath", dependency.getSystemPath());
220                setProperty(properties, "dependency." + i + ".artifactId", dependency.getArtifactId());
221                setProperty(properties, "dependency." + i + ".groupId", dependency.getNamespace());
222                setProperty(properties, "dependency." + i + ".version", dependency.getVersion());
223                setProperty(properties, "dependency." + i + ".type", dependency.getType());
224                setProperty(properties, "dependency." + i + ".optional", String.valueOf(dependency.isOptional()));
225
226                updateProjectReference(repoId, dependency.getNamespace(), dependency.getArtifactId(),
227                        dependency.getVersion(), reference);
228
229                i++;
230            }
231            Set<String> facetIds = new LinkedHashSet<>(versionMetadata.getFacetIds());
232            facetIds.addAll(Arrays.asList(properties.getProperty("facetIds", "").split(",")));
233            properties.setProperty("facetIds", join(facetIds));
234
235            updateProjectVersionFacets(versionMetadata, properties);
236
237            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
238        } catch (IOException e) {
239            log.error("Could not update project version {}, {}, {}: {}", repoId, namespace, versionMetadata.getId(), e.getMessage(), e);
240        }
241    }
242
243    private void updateProjectVersionFacets(ProjectVersionMetadata versionMetadata, Properties properties) {
244        for (MetadataFacet facet : versionMetadata.getFacetList()) {
245            for (Map.Entry<String, String> entry : facet.toProperties().entrySet()) {
246                properties.setProperty(facet.getFacetId() + ":" + entry.getKey(), entry.getValue());
247            }
248        }
249    }
250
251    private static void clearMetadataFacetProperties(Collection<MetadataFacet> facetList, Properties properties,
252                                                     String prefix) {
253        List<Object> propsToRemove = new ArrayList<>();
254        for (MetadataFacet facet : facetList) {
255            for (Object key : new ArrayList<>(properties.keySet())) {
256                String keyString = (String) key;
257                if (keyString.startsWith(prefix + facet.getFacetId() + ":")) {
258                    propsToRemove.add(key);
259                }
260            }
261        }
262
263        for (Object key : propsToRemove) {
264            properties.remove(key);
265        }
266    }
267
268    private void updateProjectReference(String repoId, String namespace, String projectId, String projectVersion,
269                                        ProjectVersionReference reference) {
270        try {
271            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
272
273            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
274            int i = Integer.parseInt(properties.getProperty("ref:lastReferenceNum", "-1")) + 1;
275            setProperty(properties, "ref:lastReferenceNum", Integer.toString(i));
276            setProperty(properties, "ref:reference." + i + ".namespace", reference.getNamespace());
277            setProperty(properties, "ref:reference." + i + ".projectId", reference.getProjectId());
278            setProperty(properties, "ref:reference." + i + ".projectVersion", reference.getProjectVersion());
279            setProperty(properties, "ref:reference." + i + ".referenceType", reference.getReferenceType().toString());
280
281            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
282        } catch (IOException e) {
283            log.error("Could not update project reference {}, {}, {}, {}: {}", repoId, namespace, projectId, projectVersion, e.getMessage(), e);
284        }
285    }
286
287    @Override
288    public void updateNamespace(RepositorySession session, String repoId, String namespace) {
289        try {
290            Path namespaceDirectory = getDirectory(repoId).resolve(namespace);
291            Properties properties = new Properties();
292            properties.setProperty("namespace", namespace);
293            writeProperties(properties, namespaceDirectory, NAMESPACE_METADATA_KEY);
294
295        } catch (IOException e) {
296            log.error("Could not update namespace of {}, {}: {}", repoId, namespace, e.getMessage(), e);
297        }
298    }
299
300    @Override
301    public List<String> getMetadataFacets(RepositorySession session, String repoId, String facetId)
302            throws MetadataRepositoryException {
303        try {
304            Path directory = getMetadataDirectory(repoId, facetId);
305            if (!(Files.exists(directory) && Files.isDirectory(directory))) {
306                return Collections.emptyList();
307            }
308            List<String> facets;
309            final String searchFile = METADATA_KEY + ".properties";
310            try (Stream<Path> fs = Files.walk(directory, FileVisitOption.FOLLOW_LINKS)) {
311                facets = fs.filter(Files::isDirectory).filter(path -> Files.exists(path.resolve(searchFile)))
312                        .map(path -> directory.relativize(path).toString()).collect(Collectors.toList());
313            }
314            return facets;
315        } catch (IOException e) {
316            throw new MetadataRepositoryException(e.getMessage(), e);
317        }
318    }
319
320    @Override
321    public <T extends MetadataFacet> Stream<T> getMetadataFacetStream(RepositorySession session, String repositoryId, Class<T> facetClazz, QueryParameter queryParameter) throws MetadataRepositoryException {
322        final MetadataFacetFactory<T> metadataFacetFactory = getFacetFactory(facetClazz);
323        if (metadataFacetFactory == null) {
324            return null;
325        }
326        final String facetId = metadataFacetFactory.getFacetId();
327        final String searchFile = METADATA_KEY + ".properties";
328        try {
329            Path directory = getMetadataDirectory(repositoryId, facetId);
330            return Files.walk(directory, FileVisitOption.FOLLOW_LINKS).filter(Files::isDirectory)
331                    .filter(path -> Files.exists(path.resolve(searchFile)))
332                    .map(path -> directory.relativize(path).toString())
333                    .sorted()
334                    .skip(queryParameter.getOffset())
335                    .limit(queryParameter.getLimit())
336                    .map(name -> getMetadataFacet(session, repositoryId, facetClazz, name));
337        } catch (IOException e) {
338            throw new MetadataRepositoryException(e.getMessage(), e);
339        }
340    }
341
342    @Override
343    public boolean hasMetadataFacet(RepositorySession session, String repositoryId, String facetId)
344            throws MetadataRepositoryException {
345
346        try {
347            Path directory = getMetadataDirectory(repositoryId, facetId);
348            if (!(Files.exists(directory) && Files.isDirectory(directory))) {
349                return false;
350            }
351            final String searchFile = METADATA_KEY + ".properties";
352            try (Stream<Path> fs = Files.walk(directory, FileVisitOption.FOLLOW_LINKS)) {
353                return fs.filter(Files::isDirectory).anyMatch(path -> Files.exists(path.resolve(searchFile)));
354            }
355        } catch (IOException e) {
356            log.error("Could not retrieve facet metatadata {}, {}: {}", repositoryId, facetId, e.getMessage(), e);
357            throw new MetadataRepositoryException(e.getMessage(), e);
358        }
359
360    }
361
362
363    @Override
364    public <T extends MetadataFacet> T getMetadataFacet(RepositorySession session, String repositoryId, Class<T> facetClazz, String name) {
365        final MetadataFacetFactory<T> metadataFacetFactory = getFacetFactory(facetClazz);
366        if (metadataFacetFactory == null) {
367            return null;
368        }
369        final String facetId = metadataFacetFactory.getFacetId();
370
371        Properties properties;
372        try {
373            properties =
374                    readProperties(getMetadataDirectory(repositoryId, facetId).resolve(name), METADATA_KEY);
375        } catch (NoSuchFileException | FileNotFoundException e) {
376            return null;
377        } catch (IOException e) {
378            log.error("Could not read properties from {}, {}: {}", repositoryId, facetId, e.getMessage(), e);
379            return null;
380        }
381        T metadataFacet = null;
382        if (metadataFacetFactory != null) {
383            metadataFacet = metadataFacetFactory.createMetadataFacet(repositoryId, name);
384            Map<String, String> map = new HashMap<>();
385            for (Object key : new ArrayList<>(properties.keySet())) {
386                String property = (String) key;
387                map.put(property, properties.getProperty(property));
388            }
389            metadataFacet.fromProperties(map);
390        }
391        return metadataFacet;
392    }
393
394
395    @Override
396    public void addMetadataFacet(RepositorySession session, String repositoryId, MetadataFacet metadataFacet) {
397        Properties properties = new Properties();
398        properties.putAll(metadataFacet.toProperties());
399
400        try {
401            Path directory =
402                    getMetadataDirectory(repositoryId, metadataFacet.getFacetId()).resolve(metadataFacet.getName());
403            writeProperties(properties, directory, METADATA_KEY);
404        } catch (IOException e) {
405            // TODO!
406            log.error(e.getMessage(), e);
407        }
408    }
409
410    @Override
411    public void removeMetadataFacets(RepositorySession session, String repositoryId, String facetId)
412            throws MetadataRepositoryException {
413        try {
414            Path dir = getMetadataDirectory(repositoryId, facetId);
415            org.apache.archiva.common.utils.FileUtils.deleteDirectory(dir);
416        } catch (IOException e) {
417            throw new MetadataRepositoryException(e.getMessage(), e);
418        }
419    }
420
421    @Override
422    public void removeMetadataFacet(RepositorySession session, String repoId, String facetId, String name)
423            throws MetadataRepositoryException {
424        try {
425            Path dir = getMetadataDirectory(repoId, facetId).resolve(name);
426            org.apache.archiva.common.utils.FileUtils.deleteDirectory(dir);
427        } catch (IOException e) {
428            throw new MetadataRepositoryException(e.getMessage(), e);
429        }
430    }
431
432    @Override
433    public List<ArtifactMetadata> getArtifactsByDateRange(RepositorySession session, String repoId, ZonedDateTime startTime, ZonedDateTime endTime)
434            throws MetadataRepositoryException {
435        try {
436            List<ArtifactMetadata> artifacts = new ArrayList<>();
437            for (String ns : getRootNamespaces(session, repoId)) {
438                getArtifactsByDateRange(session, artifacts, repoId, ns, startTime, endTime);
439            }
440            artifacts.sort(new ArtifactComparator());
441            return artifacts;
442        } catch (MetadataResolutionException e) {
443            throw new MetadataRepositoryException(e.getMessage(), e);
444        }
445    }
446
447
448    /**
449     * Result is sorted by date,
450     *
451     * @param session        The repository session
452     * @param repositoryId   The repository id
453     * @param startTime      The start time, can be <code>null</code>
454     * @param endTime        The end time, can be <code>null</code>
455     * @param queryParameter Additional parameters for the query that affect ordering and number of returned results.
456     * @return
457     * @throws MetadataRepositoryException
458     */
459    @Override
460    public Stream<ArtifactMetadata> getArtifactByDateRangeStream( RepositorySession session, String repositoryId, ZonedDateTime startTime, ZonedDateTime endTime, QueryParameter queryParameter) throws MetadataRepositoryException {
461        try {
462            List<ArtifactMetadata> artifacts = new ArrayList<>();
463            for (String ns : getRootNamespaces(session, repositoryId)) {
464                getArtifactsByDateRange(session, artifacts, repositoryId, ns, startTime, endTime);
465            }
466            Comparator<ArtifactMetadata> comp = getArtifactMetadataComparator(queryParameter, "whenGathered");
467            return artifacts.stream().sorted(comp).skip(queryParameter.getOffset()).limit(queryParameter.getLimit());
468
469        } catch (MetadataResolutionException e) {
470            throw new MetadataRepositoryException(e.getMessage(), e);
471        }
472    }
473
474
475    private void getArtifactsByDateRange(RepositorySession session, List<ArtifactMetadata> artifacts, String repoId, String ns, ZonedDateTime startTime,
476                                         ZonedDateTime endTime)
477            throws MetadataRepositoryException {
478        try {
479            for (String namespace : this.getChildNamespaces(session, repoId, ns)) {
480                getArtifactsByDateRange(session, artifacts, repoId, ns + "." + namespace, startTime, endTime);
481            }
482
483            for (String project : getProjects(session, repoId, ns)) {
484                for (String version : getProjectVersions(session, repoId, ns, project)) {
485                    for (ArtifactMetadata artifact : getArtifacts(session, repoId, ns, project, version)) {
486                        if (startTime == null || startTime.isBefore(ZonedDateTime.from(artifact.getWhenGathered().toInstant().atZone(ZoneId.systemDefault())))) {
487                            if (endTime == null || endTime.isAfter(ZonedDateTime.from(artifact.getWhenGathered().toInstant().atZone(ZoneId.systemDefault())))) {
488                                artifacts.add(artifact);
489                            }
490                        }
491                    }
492                }
493            }
494        } catch (MetadataResolutionException e) {
495            throw new MetadataRepositoryException(e.getMessage(), e);
496        }
497    }
498
499
500    @Override
501    public List<ArtifactMetadata> getArtifacts( RepositorySession session, String repoId, String namespace, String projectId,
502                                                String projectVersion)
503            throws MetadataResolutionException {
504        try {
505            Map<String, ArtifactMetadata> artifacts = new HashMap<>();
506
507            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
508
509            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
510
511            for (Map.Entry entry : properties.entrySet()) {
512                String name = (String) entry.getKey();
513                StringTokenizer tok = new StringTokenizer(name, ":");
514                if (tok.hasMoreTokens() && "artifact".equals(tok.nextToken())) {
515                    String field = tok.nextToken();
516                    String id = tok.nextToken();
517
518                    ArtifactMetadata artifact = artifacts.get(id);
519                    if (artifact == null) {
520                        artifact = new ArtifactMetadata();
521                        artifact.setRepositoryId(repoId);
522                        artifact.setNamespace(namespace);
523                        artifact.setProject(projectId);
524                        artifact.setProjectVersion(projectVersion);
525                        artifact.setVersion(projectVersion);
526                        artifact.setId(id);
527                        artifacts.put(id, artifact);
528                    }
529
530                    String value = (String) entry.getValue();
531                    if ("updated".equals(field)) {
532                        artifact.setFileLastModified(Long.parseLong(value));
533                    } else if ("size".equals(field)) {
534                        artifact.setSize(Long.valueOf(value));
535                    } else if ("whenGathered".equals(field)) {
536                        artifact.setWhenGathered(ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(value)), ZoneId.of("GMT")));
537                    } else if ("version".equals(field)) {
538                        artifact.setVersion(value);
539                    } else if (field.startsWith("checksum")) {
540                        String algorithmStr = StringUtils.removeStart( name, "artifact:checksum:"+id+":");
541                        artifact.setChecksum( ChecksumAlgorithm.valueOf( algorithmStr ), value );
542                    } else if ("facetIds".equals(field)) {
543                        if (value.length() > 0) {
544                            String propertyPrefix = "artifact:facet:" + id + ":";
545                            for (String facetId : value.split(",")) {
546                                MetadataFacetFactory factory = getFacetFactory(facetId);
547                                if (factory == null) {
548                                    log.error("Attempted to load unknown artifact metadata facet: {}", facetId);
549                                } else {
550                                    MetadataFacet facet = factory.createMetadataFacet();
551                                    String prefix = propertyPrefix + facet.getFacetId();
552                                    Map<String, String> map = new HashMap<>();
553                                    for (Object key : new ArrayList<>(properties.keySet())) {
554                                        String property = (String) key;
555                                        if (property.startsWith(prefix)) {
556                                            map.put(property.substring(prefix.length() + 1),
557                                                    properties.getProperty(property));
558                                        }
559                                    }
560                                    facet.fromProperties(map);
561                                    artifact.addFacet(facet);
562                                }
563                            }
564                        }
565
566                        updateArtifactFacets(artifact, properties);
567                    }
568                }
569            }
570            return new ArrayList<>(artifacts.values());
571        } catch (IOException e) {
572            throw new MetadataResolutionException(e.getMessage(), e);
573        }
574    }
575
576
577    @Override
578    public void close() {
579        // nothing additional to close
580    }
581
582
583    private void updateArtifactFacets(ArtifactMetadata artifact, Properties properties) {
584        String propertyPrefix = "artifact:facet:" + artifact.getId() + ":";
585        for (MetadataFacet facet : artifact.getFacetList()) {
586            for (Map.Entry<String, String> e : facet.toProperties().entrySet()) {
587                String key = propertyPrefix + facet.getFacetId() + ":" + e.getKey();
588                properties.setProperty(key, e.getValue());
589            }
590        }
591    }
592
593    @Override
594    public List<ArtifactMetadata> getArtifactsByChecksum(RepositorySession session, String repositoryId, String checksum)
595            throws MetadataRepositoryException {
596        try {
597            // TODO: this is quite slow - if we are to persist with this repository implementation we should build an index
598            //  of this information (eg. in Lucene, as before)
599            // alternatively, we could build a referential tree in the content repository, however it would need some levels
600            // of depth to avoid being too broad to be useful (eg. /repository/checksums/a/ab/abcdef1234567)
601
602            return getArtifactStream( session, repositoryId ).filter(
603                a -> a.hasChecksum( checksum )
604            ).collect( Collectors.toList() );
605        } catch (MetadataResolutionException e) {
606            throw new MetadataRepositoryException(e.getMessage(), e);
607        }
608    }
609
610    @Override
611    public void removeNamespace(RepositorySession session, String repositoryId, String project)
612            throws MetadataRepositoryException {
613        try {
614            Path namespaceDirectory = getDirectory(repositoryId).resolve(project);
615            org.apache.archiva.common.utils.FileUtils.deleteDirectory(namespaceDirectory);
616            //Properties properties = new Properties();
617            //properties.setProperty( "namespace", namespace );
618            //writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY );
619
620        } catch (IOException e) {
621            throw new MetadataRepositoryException(e.getMessage(), e);
622        }
623    }
624
625    @Override
626    public void removeTimestampedArtifact( RepositorySession session, ArtifactMetadata artifactMetadata, String baseVersion)
627            throws MetadataRepositoryException {
628
629        try {
630            Path directory = getDirectory(artifactMetadata.getRepositoryId()).resolve(
631                    artifactMetadata.getNamespace() + "/" + artifactMetadata.getProject() + "/"
632                            + baseVersion);
633
634            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
635
636            String id = artifactMetadata.getId();
637
638            properties.remove("artifact:updated:" + id);
639            properties.remove("artifact:whenGathered:" + id);
640            properties.remove("artifact:size:" + id);
641            artifactMetadata.getChecksums().entrySet().stream().forEach( entry ->
642                properties.remove( "artifact:checksum:"+id+":"+entry.getKey().name() ));
643            properties.remove("artifact:version:" + id);
644            properties.remove("artifact:facetIds:" + id);
645
646            String prefix = "artifact:facet:" + id + ":";
647            for (Object key : new ArrayList<>(properties.keySet())) {
648                String property = (String) key;
649                if (property.startsWith(prefix)) {
650                    properties.remove(property);
651                }
652            }
653
654            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
655        } catch (IOException e) {
656            throw new MetadataRepositoryException(e.getMessage(), e);
657        }
658
659    }
660
661    @Override
662    public void removeArtifact(RepositorySession session, String repoId, String namespace, String project, String version, String id)
663            throws MetadataRepositoryException {
664        try {
665            Path directory = getDirectory(repoId).resolve(namespace + "/" + project + "/" + version);
666
667            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
668
669            properties.remove("artifact:updated:" + id);
670            properties.remove("artifact:whenGathered:" + id);
671            properties.remove("artifact:size:" + id);
672            properties.remove("artifact:version:" + id);
673            properties.remove("artifact:facetIds:" + id);
674
675            String facetPrefix = "artifact:facet:" + id + ":";
676            String checksumPrefix = "artifact:checksum:"+id+":";
677            for (String property  : properties.stringPropertyNames()) {
678                if (property.startsWith( checksumPrefix )) {
679                    properties.remove( property );
680                } else if (property.startsWith(facetPrefix)) {
681                    properties.remove(property);
682                }
683            }
684
685            org.apache.archiva.common.utils.FileUtils.deleteDirectory(directory);
686            //writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
687        } catch (IOException e) {
688            throw new MetadataRepositoryException(e.getMessage(), e);
689        }
690    }
691
692    /**
693     * FIXME implements this !!!!
694     *
695     * @param session
696     * @param repositoryId
697     * @param namespace
698     * @param project
699     * @param projectVersion
700     * @param metadataFacet  will remove artifacts which have this {@link MetadataFacet} using equals
701     * @throws MetadataRepositoryException
702     */
703    @Override
704    public void removeFacetFromArtifact( RepositorySession session, String repositoryId, String namespace, String project, String projectVersion,
705                                         MetadataFacet metadataFacet)
706            throws MetadataRepositoryException {
707        throw new UnsupportedOperationException("not implemented");
708    }
709
710    @Override
711    public void removeRepository(RepositorySession session, String repoId)
712            throws MetadataRepositoryException {
713        try {
714            Path dir = getDirectory(repoId);
715            org.apache.archiva.common.utils.FileUtils.deleteDirectory(dir);
716        } catch (IOException e) {
717            throw new MetadataRepositoryException(e.getMessage(), e);
718        }
719    }
720
721
722    @Override
723    public List<ArtifactMetadata> getArtifactsByProjectVersionFacet( RepositorySession session, String key, String value, String repositoryId)
724            throws MetadataRepositoryException {
725        throw new UnsupportedOperationException("not yet implemented in File backend");
726    }
727
728    @Override
729    public List<ArtifactMetadata> getArtifactsByAttribute( RepositorySession session, String key, String value, String repositoryId)
730            throws MetadataRepositoryException {
731        throw new UnsupportedOperationException("not yet implemented in File backend");
732    }
733
734    @Override
735    public List<ArtifactMetadata> getArtifactsByProjectVersionAttribute( RepositorySession session, String key, String value, String repositoryId)
736            throws MetadataRepositoryException {
737        throw new UnsupportedOperationException("getArtifactsByProjectVersionAttribute not yet implemented in File backend");
738    }
739
740    private Path getMetadataDirectory(String repoId, String facetId)
741            throws IOException {
742        return getBaseDirectory(repoId).resolve("facets/" + facetId);
743    }
744
745    private String join(Collection<String> ids) {
746        if (ids != null && !ids.isEmpty()) {
747            StringBuilder s = new StringBuilder();
748            for (String id : ids) {
749                s.append(id);
750                s.append(",");
751            }
752            return s.substring(0, s.length() - 1);
753        }
754        return "";
755    }
756
757    private void setProperty(Properties properties, String name, String value) {
758        if (value != null) {
759            properties.setProperty(name, value);
760        }
761    }
762
763    @Override
764    public void updateArtifact(RepositorySession session, String repoId, String namespace, String projectId, String projectVersion,
765                               ArtifactMetadata artifact) {
766        try {
767            ProjectVersionMetadata metadata = new ProjectVersionMetadata();
768            metadata.setId(projectVersion);
769            updateProjectVersion(session, repoId, namespace, projectId, metadata);
770
771            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
772
773            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
774
775            clearMetadataFacetProperties(artifact.getFacetList(), properties,
776                    "artifact:facet:" + artifact.getId() + ":");
777
778            String id = artifact.getId();
779            properties.setProperty("artifact:updated:" + id,
780                    Long.toString(artifact.getFileLastModified().toInstant().toEpochMilli()));
781            properties.setProperty("artifact:whenGathered:" + id,
782                    Long.toString(artifact.getWhenGathered().toInstant().toEpochMilli()));
783            properties.setProperty("artifact:size:" + id, Long.toString(artifact.getSize()));
784            artifact.getChecksums().entrySet().stream().forEach( entry ->
785                properties.setProperty( "artifact:checksum:"+id+":"+entry.getKey().name(), entry.getValue() ));
786            properties.setProperty("artifact:version:" + id, artifact.getVersion());
787
788            Set<String> facetIds = new LinkedHashSet<>(artifact.getFacetIds());
789            String property = "artifact:facetIds:" + id;
790            facetIds.addAll(Arrays.asList(properties.getProperty(property, "").split(",")));
791            properties.setProperty(property, join(facetIds));
792
793            updateArtifactFacets(artifact, properties);
794
795            writeProperties(properties, directory, PROJECT_VERSION_METADATA_KEY);
796        } catch (IOException e) {
797            // TODO
798            log.error(e.getMessage(), e);
799        }
800    }
801
802    private Properties readOrCreateProperties(Path directory, String propertiesKey) {
803        try {
804            return readProperties(directory, propertiesKey);
805        } catch (FileNotFoundException | NoSuchFileException e) {
806            // ignore and return new properties
807        } catch (IOException e) {
808            // TODO
809            log.error(e.getMessage(), e);
810        }
811        return new Properties();
812    }
813
814    private Properties readProperties(Path directory, String propertiesKey)
815            throws IOException {
816        Properties properties = new Properties();
817        try (InputStream in = Files.newInputStream(directory.resolve(propertiesKey + ".properties"))) {
818
819            properties.load(in);
820        }
821        return properties;
822    }
823
824    @Override
825    public ProjectMetadata getProject(RepositorySession session, String repoId, String namespace, String projectId)
826            throws MetadataResolutionException {
827        try {
828            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId);
829
830            Properties properties = readOrCreateProperties(directory, PROJECT_METADATA_KEY);
831
832            ProjectMetadata project = null;
833
834            String id = properties.getProperty("id");
835            if (id != null) {
836                project = new ProjectMetadata();
837                project.setNamespace(properties.getProperty("namespace"));
838                project.setId(id);
839            }
840
841            return project;
842        } catch (IOException e) {
843            throw new MetadataResolutionException(e.getMessage(), e);
844        }
845    }
846
847    @Override
848    public ProjectVersionMetadata getProjectVersion(RepositorySession session, String repoId, String namespace, String projectId,
849                                                    String projectVersion)
850            throws MetadataResolutionException {
851        try {
852            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
853
854            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
855            String id = properties.getProperty("id");
856            ProjectVersionMetadata versionMetadata = null;
857            if (id != null) {
858                versionMetadata = new ProjectVersionMetadata();
859                versionMetadata.setId(id);
860                versionMetadata.setName(properties.getProperty("name"));
861                versionMetadata.setDescription(properties.getProperty("description"));
862                versionMetadata.setUrl(properties.getProperty("url"));
863                versionMetadata.setIncomplete(Boolean.valueOf(properties.getProperty("incomplete", "false")));
864
865                String scmConnection = properties.getProperty("scm.connection");
866                String scmDeveloperConnection = properties.getProperty("scm.developerConnection");
867                String scmUrl = properties.getProperty("scm.url");
868                if (scmConnection != null || scmDeveloperConnection != null || scmUrl != null) {
869                    Scm scm = new Scm();
870                    scm.setConnection(scmConnection);
871                    scm.setDeveloperConnection(scmDeveloperConnection);
872                    scm.setUrl(scmUrl);
873                    versionMetadata.setScm(scm);
874                }
875
876                String ciSystem = properties.getProperty("ci.system");
877                String ciUrl = properties.getProperty("ci.url");
878                if (ciSystem != null || ciUrl != null) {
879                    CiManagement ci = new CiManagement();
880                    ci.setSystem(ciSystem);
881                    ci.setUrl(ciUrl);
882                    versionMetadata.setCiManagement(ci);
883                }
884
885                String issueSystem = properties.getProperty("issue.system");
886                String issueUrl = properties.getProperty("issue.url");
887                if (issueSystem != null || issueUrl != null) {
888                    IssueManagement issueManagement = new IssueManagement();
889                    issueManagement.setSystem(issueSystem);
890                    issueManagement.setUrl(issueUrl);
891                    versionMetadata.setIssueManagement(issueManagement);
892                }
893
894                String orgName = properties.getProperty("org.name");
895                String orgUrl = properties.getProperty("org.url");
896                if (orgName != null || orgUrl != null) {
897                    Organization org = new Organization();
898                    org.setName(orgName);
899                    org.setUrl(orgUrl);
900                    versionMetadata.setOrganization(org);
901                }
902
903                boolean done = false;
904                int i = 0;
905                while (!done) {
906                    String licenseName = properties.getProperty("license." + i + ".name");
907                    String licenseUrl = properties.getProperty("license." + i + ".url");
908                    if (licenseName != null || licenseUrl != null) {
909                        License license = new License();
910                        license.setName(licenseName);
911                        license.setUrl(licenseUrl);
912                        versionMetadata.addLicense(license);
913                    } else {
914                        done = true;
915                    }
916                    i++;
917                }
918
919                done = false;
920                i = 0;
921                while (!done) {
922                    String mailingListName = properties.getProperty("mailingList." + i + ".name");
923                    if (mailingListName != null) {
924                        MailingList mailingList = new MailingList();
925                        mailingList.setName(mailingListName);
926                        mailingList.setMainArchiveUrl(properties.getProperty("mailingList." + i + ".archive"));
927                        String p = properties.getProperty("mailingList." + i + ".otherArchives");
928                        if (p != null && p.length() > 0) {
929                            mailingList.setOtherArchives(Arrays.asList(p.split(",")));
930                        } else {
931                            mailingList.setOtherArchives(Collections.emptyList());
932                        }
933                        mailingList.setPostAddress(properties.getProperty("mailingList." + i + ".post"));
934                        mailingList.setSubscribeAddress(properties.getProperty("mailingList." + i + ".subscribe"));
935                        mailingList.setUnsubscribeAddress(
936                                properties.getProperty("mailingList." + i + ".unsubscribe"));
937                        versionMetadata.addMailingList(mailingList);
938                    } else {
939                        done = true;
940                    }
941                    i++;
942                }
943
944                done = false;
945                i = 0;
946                while (!done) {
947                    String dependencyArtifactId = properties.getProperty("dependency." + i + ".artifactId");
948                    if (dependencyArtifactId != null) {
949                        Dependency dependency = new Dependency();
950                        dependency.setArtifactId(dependencyArtifactId);
951                        dependency.setNamespace(properties.getProperty("dependency." + i + ".groupId"));
952                        dependency.setClassifier(properties.getProperty("dependency." + i + ".classifier"));
953                        dependency.setOptional(
954                                Boolean.valueOf(properties.getProperty("dependency." + i + ".optional")));
955                        dependency.setScope(properties.getProperty("dependency." + i + ".scope"));
956                        dependency.setSystemPath(properties.getProperty("dependency." + i + ".systemPath"));
957                        dependency.setType(properties.getProperty("dependency." + i + ".type"));
958                        dependency.setVersion(properties.getProperty("dependency." + i + ".version"));
959                        dependency.setOptional(
960                                Boolean.valueOf(properties.getProperty("dependency." + i + ".optional")));
961                        versionMetadata.addDependency(dependency);
962                    } else {
963                        done = true;
964                    }
965                    i++;
966                }
967
968                String facetIds = properties.getProperty("facetIds", "");
969                if (facetIds.length() > 0) {
970                    for (String facetId : facetIds.split(",")) {
971                        MetadataFacetFactory factory = getFacetFactory(facetId);
972                        if (factory == null) {
973                            log.error("Attempted to load unknown project version metadata facet: {}", facetId);
974                        } else {
975                            MetadataFacet facet = factory.createMetadataFacet();
976                            Map<String, String> map = new HashMap<>();
977                            for (Object key : new ArrayList<>(properties.keySet())) {
978                                String property = (String) key;
979                                if (property.startsWith(facet.getFacetId())) {
980                                    map.put(property.substring(facet.getFacetId().length() + 1),
981                                            properties.getProperty(property));
982                                }
983                            }
984                            facet.fromProperties(map);
985                            versionMetadata.addFacet(facet);
986                        }
987                    }
988                }
989
990                updateProjectVersionFacets(versionMetadata, properties);
991            }
992            return versionMetadata;
993        } catch (IOException e) {
994            throw new MetadataResolutionException(e.getMessage(), e);
995        }
996    }
997
998    @Override
999    public List<String> getArtifactVersions( RepositorySession session, String repoId, String namespace, String projectId,
1000                                             String projectVersion)
1001            throws MetadataResolutionException {
1002        try {
1003            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
1004
1005            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
1006
1007            Set<String> versions = new HashSet<>();
1008            for (Map.Entry entry : properties.entrySet()) {
1009                String name = (String) entry.getKey();
1010                if (name.startsWith("artifact:version:")) {
1011                    versions.add((String) entry.getValue());
1012                }
1013            }
1014            return new ArrayList<>( versions );
1015        } catch (IOException e) {
1016            throw new MetadataResolutionException(e.getMessage(), e);
1017        }
1018    }
1019
1020    @Override
1021    public List<ProjectVersionReference> getProjectReferences( RepositorySession session, String repoId, String namespace, String projectId,
1022                                                               String projectVersion)
1023            throws MetadataResolutionException {
1024        try {
1025            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
1026
1027            Properties properties = readOrCreateProperties(directory, PROJECT_VERSION_METADATA_KEY);
1028            int numberOfRefs = Integer.parseInt(properties.getProperty("ref:lastReferenceNum", "-1")) + 1;
1029
1030            List<ProjectVersionReference> references = new ArrayList<>();
1031            for (int i = 0; i < numberOfRefs; i++) {
1032                ProjectVersionReference reference = new ProjectVersionReference();
1033                reference.setProjectId(properties.getProperty("ref:reference." + i + ".projectId"));
1034                reference.setNamespace(properties.getProperty("ref:reference." + i + ".namespace"));
1035                reference.setProjectVersion(properties.getProperty("ref:reference." + i + ".projectVersion"));
1036                reference.setReferenceType(ProjectVersionReference.ReferenceType.valueOf(
1037                        properties.getProperty("ref:reference." + i + ".referenceType")));
1038                references.add(reference);
1039            }
1040            return references;
1041        } catch (IOException e) {
1042            throw new MetadataResolutionException(e.getMessage(), e);
1043        }
1044    }
1045
1046    @Override
1047    public List<String> getRootNamespaces( RepositorySession session, String repoId)
1048            throws MetadataResolutionException {
1049        return this.getChildNamespaces(session, repoId, null);
1050    }
1051
1052    private Stream<String> getAllNamespacesStream(RepositorySession session, String repoId) {
1053        Path directory = null;
1054        try
1055        {
1056            directory = getDirectory(repoId);
1057        }
1058        catch ( IOException e )
1059        {
1060            return Stream.empty( );
1061        }
1062        if (!(Files.exists(directory) && Files.isDirectory(directory))) {
1063            return Stream.empty( );
1064        }
1065        final String searchFile = NAMESPACE_METADATA_KEY + ".properties";
1066        try
1067        {
1068            return  Files.list(directory).filter(Files::isDirectory).filter(path ->
1069                    Files.exists(path.resolve(searchFile))
1070                ).map(path -> path.getFileName().toString());
1071        }
1072        catch ( IOException e )
1073        {
1074            return Stream.empty( );
1075        }
1076    }
1077
1078    @Override
1079    public List<String> getChildNamespaces( RepositorySession session, String repoId, String baseNamespace)
1080            throws MetadataResolutionException {
1081        try {
1082            List<String> allNamespaces;
1083            Path directory = getDirectory(repoId);
1084            if (!(Files.exists(directory) && Files.isDirectory(directory))) {
1085                return Collections.emptyList();
1086            }
1087            final String searchFile = NAMESPACE_METADATA_KEY + ".properties";
1088            try (Stream<Path> fs = Files.list(directory)) {
1089                allNamespaces = fs.filter(Files::isDirectory).filter(path ->
1090                        Files.exists(path.resolve(searchFile))
1091                ).map(path -> path.getFileName().toString()).collect(Collectors.toList());
1092            }
1093
1094            Set<String> namespaces = new LinkedHashSet<>();
1095            int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
1096            for (String namespace : allNamespaces) {
1097                if (baseNamespace == null || namespace.startsWith(baseNamespace + ".")) {
1098                    int i = namespace.indexOf('.', fromIndex);
1099                    if (i >= 0) {
1100                        namespaces.add(namespace.substring(fromIndex, i));
1101                    } else {
1102                        namespaces.add(namespace.substring(fromIndex));
1103                    }
1104                }
1105            }
1106            return new ArrayList<>(namespaces);
1107        } catch (IOException e) {
1108            throw new MetadataResolutionException(e.getMessage(), e);
1109        }
1110    }
1111
1112    @Override
1113    public List<String> getProjects( RepositorySession session, String repoId, String namespace)
1114            throws MetadataResolutionException {
1115        try {
1116            List<String> projects;
1117            Path directory = getDirectory(repoId).resolve(namespace);
1118            if (!(Files.exists(directory) && Files.isDirectory(directory))) {
1119                return Collections.emptyList();
1120            }
1121            final String searchFile = PROJECT_METADATA_KEY + ".properties";
1122            try (Stream<Path> fs = Files.list(directory)) {
1123                projects = fs.filter(Files::isDirectory).filter(path ->
1124                        Files.exists(path.resolve(searchFile))
1125                ).map(path -> path.getFileName().toString()).collect(Collectors.toList());
1126            }
1127
1128            return projects;
1129        } catch (IOException e) {
1130            throw new MetadataResolutionException(e.getMessage(), e);
1131        }
1132    }
1133
1134    @Override
1135    public List<String> getProjectVersions( RepositorySession session, String repoId, String namespace, String projectId)
1136            throws MetadataResolutionException {
1137        try {
1138            List<String> projectVersions;
1139            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId);
1140            if (!(Files.exists(directory) && Files.isDirectory(directory))) {
1141                return Collections.emptyList();
1142            }
1143            final String searchFile = PROJECT_VERSION_METADATA_KEY + ".properties";
1144            try (Stream<Path> fs = Files.list(directory)) {
1145                projectVersions = fs.filter(Files::isDirectory).filter(path ->
1146                        Files.exists(path.resolve(searchFile))
1147                ).map(path -> path.getFileName().toString()).collect(Collectors.toList());
1148            }
1149            return projectVersions;
1150        } catch (IOException e) {
1151            throw new MetadataResolutionException(e.getMessage(), e);
1152        }
1153    }
1154
1155    @Override
1156    public void removeProject(RepositorySession session, String repositoryId, String namespace, String projectId)
1157            throws MetadataRepositoryException {
1158        try {
1159            Path directory = getDirectory(repositoryId).resolve(namespace + "/" + projectId);
1160            org.apache.archiva.common.utils.FileUtils.deleteDirectory(directory);
1161        } catch (IOException e) {
1162            throw new MetadataRepositoryException(e.getMessage(), e);
1163        }
1164    }
1165
1166    @Override
1167    public void removeProjectVersion(RepositorySession session, String repoId, String namespace, String projectId, String projectVersion)
1168            throws MetadataRepositoryException {
1169        try {
1170            Path directory = getDirectory(repoId).resolve(namespace + "/" + projectId + "/" + projectVersion);
1171            org.apache.archiva.common.utils.FileUtils.deleteDirectory(directory);
1172        } catch (IOException e) {
1173            throw new MetadataRepositoryException(e.getMessage(), e);
1174        }
1175
1176    }
1177
1178    private void writeProperties(Properties properties, Path directory, String propertiesKey)
1179            throws IOException {
1180        Files.createDirectories(directory);
1181        try (OutputStream os = Files.newOutputStream(directory.resolve(propertiesKey + ".properties"))) {
1182            properties.store(os, null);
1183        }
1184    }
1185
1186    private static class ArtifactComparator
1187            implements Comparator<ArtifactMetadata> {
1188        @Override
1189        public int compare(ArtifactMetadata artifact1, ArtifactMetadata artifact2) {
1190            if (artifact1.getWhenGathered() == artifact2.getWhenGathered()) {
1191                return 0;
1192            }
1193            if (artifact1.getWhenGathered() == null) {
1194                return 1;
1195            }
1196            if (artifact2.getWhenGathered() == null) {
1197                return -1;
1198            }
1199            return artifact1.getWhenGathered().compareTo(artifact2.getWhenGathered());
1200        }
1201    }
1202
1203    @Override
1204    public List<ArtifactMetadata> getArtifacts(RepositorySession session, String repoId)
1205            throws MetadataRepositoryException {
1206        try {
1207            List<ArtifactMetadata> artifacts = new ArrayList<>();
1208            for (String ns : getRootNamespaces(session, repoId)) {
1209                getArtifacts(session, artifacts, repoId, ns);
1210            }
1211            return artifacts;
1212        } catch (MetadataResolutionException e) {
1213            throw new MetadataRepositoryException(e.getMessage(), e);
1214        }
1215    }
1216
1217    private class ArtifactCoordinates {
1218        final String namespace;
1219        final String project;
1220        final String version;
1221
1222        public ArtifactCoordinates(String namespace, String project, String version) {
1223            this.namespace = namespace;
1224            this.project = project;
1225            this.version = version;
1226        }
1227
1228        public String getNamespace( )
1229        {
1230            return namespace;
1231        }
1232
1233        public String getProject( )
1234        {
1235            return project;
1236        }
1237
1238        public String getVersion( )
1239        {
1240            return version;
1241        }
1242    }
1243
1244    @Override
1245    public Stream<ArtifactMetadata> getArtifactStream(  final RepositorySession session,  final String repositoryId,
1246                                                        QueryParameter queryParameter ) throws MetadataResolutionException
1247    {
1248
1249        return getAllNamespacesStream( session, repositoryId ).filter( Objects::nonNull ).flatMap( ns ->
1250            {
1251                try
1252                {
1253                    return getProjects( session, repositoryId, ns ).stream( ).map( proj ->
1254                        new ArtifactCoordinates( ns, proj, null ) );
1255                }
1256                catch ( MetadataResolutionException e )
1257                {
1258                    return null;
1259                }
1260            }
1261        ).filter( Objects::nonNull ).flatMap( artifactCoordinates ->
1262            {
1263                try
1264                {
1265                    return getProjectVersions( session, repositoryId, artifactCoordinates.getNamespace( ), artifactCoordinates.getProject( ) )
1266                        .stream( ).map(version -> new ArtifactCoordinates( artifactCoordinates.getNamespace(), artifactCoordinates.getProject(), version ));
1267                }
1268                catch ( MetadataResolutionException e )
1269                {
1270                    return null;
1271                }
1272            }
1273        ).filter( Objects::nonNull ).flatMap( ac ->
1274            {
1275                try
1276                {
1277                    return getArtifactStream( session, repositoryId, ac.getNamespace(), ac.getProject(), ac.getVersion() );
1278                }
1279                catch ( MetadataResolutionException e )
1280                {
1281                    return null;
1282                }
1283            }
1284            ).filter( Objects::nonNull );
1285    }
1286
1287    @Override
1288    public Stream<ArtifactMetadata> getArtifactStream( final RepositorySession session, final String repoId,
1289                                                       final String namespace, final String projectId,
1290                                                       final String projectVersion ) throws MetadataResolutionException
1291    {
1292        return getArtifacts( session, repoId, namespace, projectId, projectVersion ).stream( );
1293    }
1294
1295    private void getArtifacts(RepositorySession session, List<ArtifactMetadata> artifacts, String repoId, String ns)
1296            throws MetadataResolutionException {
1297        for (String namespace : this.getChildNamespaces(session, repoId, ns)) {
1298            getArtifacts(session, artifacts, repoId, ns + "." + namespace);
1299        }
1300
1301        for (String project : getProjects(session, repoId, ns)) {
1302            for (String version : getProjectVersions(session, repoId, ns, project)) {
1303                artifacts.addAll(getArtifacts(session, repoId, ns, project, version));
1304            }
1305        }
1306    }
1307
1308    @Override
1309    public List<ArtifactMetadata> searchArtifacts(RepositorySession session, String repositoryId, String text, boolean exact) {
1310        throw new UnsupportedOperationException("searchArtifacts not yet implemented in File backend");
1311    }
1312
1313    @Override
1314    public List<ArtifactMetadata> searchArtifacts(RepositorySession session, String repositoryId, String key, String text, boolean exact) {
1315        throw new UnsupportedOperationException("searchArtifacts not yet implemented in File backend");
1316    }
1317}