]> source.dussan.org Git - archiva.git/blob
e694ec915168da6075ab6a0b1b051eb11744dc0d
[archiva.git] /
1 package org.apache.archiva.metadata.repository.file;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.checksum.ChecksumAlgorithm;
23 import org.apache.archiva.configuration.ArchivaConfiguration;
24 import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
25 import org.apache.archiva.metadata.QueryParameter;
26 import org.apache.archiva.metadata.model.ArtifactMetadata;
27 import org.apache.archiva.metadata.model.CiManagement;
28 import org.apache.archiva.metadata.model.Dependency;
29 import org.apache.archiva.metadata.model.IssueManagement;
30 import org.apache.archiva.metadata.model.License;
31 import org.apache.archiva.metadata.model.MailingList;
32 import org.apache.archiva.metadata.model.MetadataFacet;
33 import org.apache.archiva.metadata.model.MetadataFacetFactory;
34 import org.apache.archiva.metadata.model.Organization;
35 import org.apache.archiva.metadata.model.ProjectMetadata;
36 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
37 import org.apache.archiva.metadata.model.ProjectVersionReference;
38 import org.apache.archiva.metadata.model.Scm;
39 import org.apache.archiva.metadata.repository.AbstractMetadataRepository;
40 import org.apache.archiva.metadata.repository.MetadataRepository;
41 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
42 import org.apache.archiva.metadata.repository.MetadataResolutionException;
43 import org.apache.archiva.metadata.repository.MetadataService;
44 import org.apache.archiva.metadata.repository.RepositorySession;
45 import org.apache.commons.lang.StringUtils;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import javax.annotation.ParametersAreNonnullByDefault;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.nio.file.FileVisitOption;
55 import java.nio.file.Files;
56 import java.nio.file.NoSuchFileException;
57 import java.nio.file.Path;
58 import java.nio.file.Paths;
59 import java.time.Instant;
60 import java.time.ZoneId;
61 import java.time.ZonedDateTime;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collection;
65 import java.util.Collections;
66 import java.util.Comparator;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.LinkedHashSet;
70 import java.util.List;
71 import java.util.Map;
72 import java.util.Objects;
73 import java.util.Properties;
74 import java.util.Set;
75 import java.util.StringTokenizer;
76 import java.util.stream.Collectors;
77 import java.util.stream.Stream;
78
79 /**
80  * File implementation of the metadata repository. It uses property files in a separate directory tree.
81  * The implementation has no fulltext index. So fulltext queries are not supported.
82  *
83  * Some retrieval methods may not be very efficient.
84  */
85 @ParametersAreNonnullByDefault
86 public class FileMetadataRepository
87         extends AbstractMetadataRepository implements MetadataRepository {
88
89     private final ArchivaConfiguration configuration;
90
91     private Logger log = LoggerFactory.getLogger(FileMetadataRepository.class);
92
93     private static final String PROJECT_METADATA_KEY = "project-metadata";
94
95     private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata";
96
97     private static final String NAMESPACE_METADATA_KEY = "namespace-metadata";
98
99     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.getGroupId());
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.getGroupId(), 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 Collection<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 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.setGroupId(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 Collection<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 versions;
1015         } catch (IOException e) {
1016             throw new MetadataResolutionException(e.getMessage(), e);
1017         }
1018     }
1019
1020     @Override
1021     public Collection<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 Collection<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 Collection<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 Collection<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 Collection<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 }