]> source.dussan.org Git - archiva.git/blob
811fb0733e610bee126dde1e236f528be407a0f0
[archiva.git] /
1 package org.apache.archiva.metadata.repository.jcr;
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.metadata.QueryParameter;
24 import org.apache.archiva.metadata.maven.model.MavenArtifactFacet;
25 import org.apache.archiva.metadata.model.ArtifactMetadata;
26 import org.apache.archiva.metadata.model.CiManagement;
27 import org.apache.archiva.metadata.model.Dependency;
28 import org.apache.archiva.metadata.model.FacetedMetadata;
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.ModelInfo;
35 import org.apache.archiva.metadata.model.Organization;
36 import org.apache.archiva.metadata.model.ProjectMetadata;
37 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
38 import org.apache.archiva.metadata.model.ProjectVersionReference;
39 import org.apache.archiva.metadata.model.Scm;
40 import org.apache.archiva.metadata.repository.AbstractMetadataRepository;
41 import org.apache.archiva.metadata.repository.MetadataRepository;
42 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
43 import org.apache.archiva.metadata.repository.MetadataResolutionException;
44 import org.apache.archiva.metadata.repository.MetadataService;
45 import org.apache.archiva.metadata.repository.MetadataSessionException;
46 import org.apache.archiva.metadata.repository.RepositorySession;
47 import org.apache.archiva.metadata.repository.stats.model.RepositoryStatistics;
48 import org.apache.archiva.metadata.repository.stats.model.RepositoryStatisticsProvider;
49 import org.apache.commons.lang3.StringUtils;
50 import org.apache.jackrabbit.commons.JcrUtils;
51 import org.apache.jackrabbit.commons.cnd.CndImporter;
52 import org.apache.jackrabbit.commons.cnd.ParseException;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import javax.jcr.NamespaceRegistry;
57 import javax.jcr.Node;
58 import javax.jcr.NodeIterator;
59 import javax.jcr.PathNotFoundException;
60 import javax.jcr.Property;
61 import javax.jcr.Repository;
62 import javax.jcr.RepositoryException;
63 import javax.jcr.Session;
64 import javax.jcr.SimpleCredentials;
65 import javax.jcr.Value;
66 import javax.jcr.ValueFactory;
67 import javax.jcr.Workspace;
68 import javax.jcr.query.Query;
69 import javax.jcr.query.QueryManager;
70 import javax.jcr.query.QueryResult;
71 import javax.jcr.query.Row;
72 import javax.jcr.query.RowIterator;
73 import java.io.IOException;
74 import java.io.InputStreamReader;
75 import java.io.Reader;
76 import java.time.ZonedDateTime;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Calendar;
80 import java.util.Collection;
81 import java.util.Collections;
82 import java.util.GregorianCalendar;
83 import java.util.HashMap;
84 import java.util.HashSet;
85 import java.util.Iterator;
86 import java.util.LinkedHashSet;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.Map.Entry;
90 import java.util.Optional;
91 import java.util.Set;
92 import java.util.Spliterator;
93 import java.util.function.Consumer;
94 import java.util.function.Function;
95 import java.util.stream.Collectors;
96 import java.util.stream.Stream;
97 import java.util.stream.StreamSupport;
98
99 import static javax.jcr.Property.JCR_LAST_MODIFIED;
100 import static org.apache.archiva.metadata.repository.jcr.JcrConstants.*;
101
102 /**
103  * TODO below: revise storage format for project version metadata
104  * TODO revise reference storage
105  */
106 public class JcrMetadataRepository
107         extends AbstractMetadataRepository implements MetadataRepository, RepositoryStatisticsProvider {
108
109
110     private static final String QUERY_ARTIFACT_1 = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/";
111
112     static final String QUERY_ARTIFACTS_BY_PROJECT_VERSION_1 = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
113             + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) INNER JOIN [" + FACET_NODE_TYPE
114             + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ([facet].[";
115     static final String QUERY_ARTIFACTS_BY_PROJECT_VERSION_2 = "] = $value)";
116
117     static final String QUERY_ARTIFACTS_BY_METADATA_1 = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact INNER JOIN [" + FACET_NODE_TYPE
118             + "] AS facet ON ISCHILDNODE(facet, artifact) WHERE ([facet].[";
119     static final String QUERY_ARTIFACTS_BY_METADATA_2 = "] = $value)";
120
121     static final String QUERY_ARTIFACTS_BY_PROPERTY_1 = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
122             + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE ([projectVersion].[";
123     static final String QUERY_ARTIFACTS_BY_PROPERTY_2 = "] = $value)";
124
125
126     private static final String QUERY_ARTIFACT_2 = "')";
127
128     private Logger log = LoggerFactory.getLogger(JcrMetadataRepository.class);
129
130     private Repository repository;
131
132     public JcrMetadataRepository(MetadataService metadataService, Repository repository)
133             throws RepositoryException {
134         super(metadataService);
135         this.repository = repository;
136     }
137
138
139     public static void initializeNodeTypes(Session session)
140             throws RepositoryException {
141
142         // TODO: consider using namespaces for facets instead of the current approach:
143         // (if used, check if actually called by normal injection)
144 //        for ( String facetId : metadataFacetFactories.keySet() )
145 //        {
146 //            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
147 //        }
148         Workspace workspace = session.getWorkspace();
149         NamespaceRegistry registry = workspace.getNamespaceRegistry();
150
151         if (!Arrays.asList(registry.getPrefixes()).contains("archiva")) {
152             registry.registerNamespace("archiva", "http://archiva.apache.org/jcr/");
153         }
154
155         try (
156                 Reader cndReader = new InputStreamReader(
157                         Thread.currentThread().getContextClassLoader().getResourceAsStream("org/apache/archiva/metadata/repository/jcr/jcr-schema.cnd"))) {
158             CndImporter.registerNodeTypes(cndReader, session);
159         } catch (ParseException e) {
160             e.printStackTrace();
161         } catch (IOException e) {
162             e.printStackTrace();
163         }
164
165     }
166
167     private Session getSession(RepositorySession repositorySession) throws MetadataRepositoryException {
168         if (repositorySession instanceof JcrRepositorySession) {
169             return ((JcrRepositorySession) repositorySession).getJcrSession();
170         } else {
171             throw new MetadataRepositoryException("The given session object is not a JcrSession instance: " + repositorySession.getClass().getName());
172         }
173     }
174
175     @Override
176     public void updateProject(RepositorySession session, String repositoryId, ProjectMetadata project)
177             throws MetadataRepositoryException {
178         final Session jcrSession = getSession(session);
179         updateProject(jcrSession, repositoryId, project.getNamespace(), project.getId());
180     }
181
182     private void updateProject(Session jcrSession, String repositoryId, String namespace, String projectId)
183             throws MetadataRepositoryException {
184         updateNamespace(jcrSession, repositoryId, namespace);
185
186         try {
187             getOrAddProjectNode(jcrSession, repositoryId, namespace, projectId);
188         } catch (RepositoryException e) {
189             throw new MetadataRepositoryException(e.getMessage(), e);
190         }
191     }
192
193     @Override
194     public void updateArtifact(RepositorySession session, String repositoryId, String namespace, String projectId, String projectVersion,
195                                ArtifactMetadata artifactMeta)
196             throws MetadataRepositoryException {
197         final Session jcrSession = getSession(session);
198         updateNamespace(session, repositoryId, namespace);
199
200         try {
201             Node node =
202                     getOrAddArtifactNode(jcrSession, repositoryId, namespace, projectId, projectVersion, artifactMeta.getId());
203
204             node.setProperty("id", artifactMeta.getId());
205             Calendar cal = GregorianCalendar.from(artifactMeta.getFileLastModified());
206             node.setProperty(JCR_LAST_MODIFIED, cal);
207
208             cal = GregorianCalendar.from(artifactMeta.getWhenGathered());
209             node.setProperty("whenGathered", cal);
210
211             node.setProperty("size", artifactMeta.getSize());
212
213             int idx = 0;
214             Node cslistNode = getOrAddNodeByPath(node, "checksums", CHECKSUMS_FOLDER_TYPE, true);
215             NodeIterator nit = cslistNode.getNodes("*");
216             while (nit.hasNext()) {
217                 Node csNode = nit.nextNode();
218                 if (csNode.isNodeType(CHECKSUM_NODE_TYPE)) {
219                     csNode.remove();
220                 }
221             }
222             for (Map.Entry<ChecksumAlgorithm, String> entry : artifactMeta.getChecksums().entrySet()) {
223                 String type = entry.getKey().name();
224                 Node csNode = cslistNode.addNode(type, CHECKSUM_NODE_TYPE);
225                 csNode.setProperty("type", type);
226                 csNode.setProperty("value", entry.getValue());
227             }
228
229             node.setProperty("version", artifactMeta.getVersion());
230
231             // iterate over available facets to update/add/remove from the artifactMetadata
232             for (String facetId : metadataService.getSupportedFacets()) {
233                 MetadataFacet metadataFacet = artifactMeta.getFacet(facetId);
234                 if (metadataFacet == null) {
235                     continue;
236                 }
237                 if (node.hasNode(facetId)) {
238                     node.getNode(facetId).remove();
239                 }
240                 if (metadataFacet != null) {
241                     // recreate, to ensure properties are removed
242                     Node n = node.addNode(facetId, FACET_NODE_TYPE);
243                     n.setProperty("facetId", facetId);
244
245                     for (Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet()) {
246                         n.setProperty(entry.getKey(), entry.getValue());
247                     }
248                 }
249             }
250         } catch (RepositoryException e) {
251             throw new MetadataRepositoryException(e.getMessage(), e);
252         }
253     }
254
255     @Override
256     public void updateProjectVersion(RepositorySession session, String repositoryId, String namespace, String projectId,
257                                      ProjectVersionMetadata versionMetadata)
258             throws MetadataRepositoryException {
259         final Session jcrSession = getSession(session);
260         updateProject(jcrSession, repositoryId, namespace, projectId);
261
262         try {
263             Node versionNode =
264                     getOrAddProjectVersionNode(jcrSession, repositoryId, namespace, projectId, versionMetadata.getId());
265             versionNode.setProperty("id", versionMetadata.getId());
266             versionNode.setProperty("name", StringUtils.isEmpty(versionMetadata.getName()) ? "" : versionMetadata.getName());
267             versionNode.setProperty("description", StringUtils.isEmpty(versionMetadata.getDescription()) ? "" : versionMetadata.getDescription());
268             versionNode.setProperty("url", versionMetadata.getUrl());
269             versionNode.setProperty("incomplete", versionMetadata.isIncomplete());
270
271             // FIXME: decide how to treat these in the content repo
272             if (versionMetadata.getScm() != null) {
273                 versionNode.setProperty("scm.connection", versionMetadata.getScm().getConnection());
274                 versionNode.setProperty("scm.developerConnection", versionMetadata.getScm().getDeveloperConnection());
275                 versionNode.setProperty("scm.url", versionMetadata.getScm().getUrl());
276             }
277             if (versionMetadata.getCiManagement() != null) {
278                 versionNode.setProperty("ci.system", versionMetadata.getCiManagement().getSystem());
279                 versionNode.setProperty("ci.url", versionMetadata.getCiManagement().getUrl());
280             }
281             if (versionMetadata.getIssueManagement() != null) {
282                 versionNode.setProperty("issue.system", versionMetadata.getIssueManagement().getSystem());
283                 versionNode.setProperty("issue.url", versionMetadata.getIssueManagement().getUrl());
284             }
285             if (versionMetadata.getOrganization() != null) {
286                 versionNode.setProperty("org.name", versionMetadata.getOrganization().getName());
287                 versionNode.setProperty("org.url", versionMetadata.getOrganization().getUrl());
288             }
289             int i = 0;
290             Node licensesNode = JcrUtils.getOrAddNode(versionNode, "licenses", LICENSES_FOLDER_TYPE);
291             Set<String> licNames = new HashSet<>();
292             for (License license : versionMetadata.getLicenses()) {
293                 Node licNode = JcrUtils.getOrAddNode(licensesNode, license.getName(), LICENSE_NODE_TYPE);
294                 licNode.setProperty("index", i);
295                 licNode.setProperty("name", license.getName());
296                 licNode.setProperty("url", license.getUrl());
297                 licNames.add(license.getName());
298                 i++;
299             }
300             NodeIterator nodeIterator = licensesNode.getNodes();
301             while (nodeIterator.hasNext()) {
302                 Node n = nodeIterator.nextNode();
303                 if (!licNames.contains(n.getName())) {
304                     n.remove();
305                 }
306             }
307             i = 0;
308             Node mailinglistsListNode = JcrUtils.getOrAddNode(versionNode, "mailinglists", MAILINGLISTS_FOLDER_TYPE);
309             Set<String> listNames = new HashSet<>();
310             for (MailingList mailingList : versionMetadata.getMailingLists()) {
311                 final String name = mailingList.getName();
312                 Node mailNode = JcrUtils.getOrAddNode(mailinglistsListNode, mailingList.getName(), MAILINGLIST_NODE_TYPE);
313                 mailNode.setProperty("index", i);
314                 mailNode.setProperty("archive", mailingList.getMainArchiveUrl());
315                 mailNode.setProperty("name", mailingList.getName());
316                 mailNode.setProperty("post", mailingList.getPostAddress());
317                 mailNode.setProperty("unsubscribe", mailingList.getUnsubscribeAddress());
318                 mailNode.setProperty("subscribe", mailingList.getSubscribeAddress());
319                 mailNode.setProperty("otherArchives",
320                         join(mailingList.getOtherArchives()));
321                 i++;
322                 listNames.add(name);
323             }
324             nodeIterator = mailinglistsListNode.getNodes();
325             while (nodeIterator.hasNext()) {
326                 Node n = nodeIterator.nextNode();
327                 if (!listNames.contains(n.getName())) {
328                     n.remove();
329                 }
330             }
331             if (!versionMetadata.getDependencies().isEmpty()) {
332                 Node dependenciesNode = JcrUtils.getOrAddNode(versionNode, "dependencies", DEPENDENCIES_FOLDER_TYPE);
333
334                 for (Dependency dependency : versionMetadata.getDependencies()) {
335                     // Note that we deliberately don't alter the namespace path - not enough dependencies for
336                     // number of nodes at a given depth to be an issue. Similarly, we don't add subnodes for each
337                     // component of the ID as that creates extra depth and causes a great cost in space and memory
338
339                     // FIXME: change to artifact's ID - this is constructed by the Maven 2 format for now.
340                     //        This won't support types where the extension doesn't match the type.
341                     //        (see also Maven2RepositoryStorage#readProjectVersionMetadata construction of POM)
342                     String id =
343                             dependency.getNamespace() + ";" + dependency.getArtifactId() + "-" + dependency.getVersion();
344                     if (dependency.getClassifier() != null) {
345                         id += "-" + dependency.getClassifier();
346                     }
347                     id += "." + dependency.getType();
348
349                     Node n = JcrUtils.getOrAddNode(dependenciesNode, id, DEPENDENCY_NODE_TYPE);
350                     n.setProperty("id", id);
351
352                     n.setProperty("namespace", dependency.getNamespace());
353                     n.setProperty("artifactId", dependency.getArtifactId());
354                     n.setProperty("version", dependency.getVersion());
355                     n.setProperty("type", dependency.getType());
356                     n.setProperty("classifier", dependency.getClassifier());
357                     n.setProperty("scope", dependency.getScope());
358                     n.setProperty("systemPath", dependency.getSystemPath());
359                     n.setProperty("optional", dependency.isOptional());
360                     n.setProperty("projectId", dependency.getProjectId());
361                     // TODO: Fixig
362                     Node refNode = findArtifactNode(jcrSession, dependency.getNamespace(),
363                             dependency.getProjectId(), dependency.getVersion(), dependency.getArtifactId());
364                     if (refNode!=null) {
365                         n.setProperty("link", refNode.getPath());
366                     }
367
368                     // node has no native content at this time, just facets
369                     // no need to list a type as it's implied by the path. Parents are Maven specific.
370
371                     // FIXME: add scope, systemPath, type, version, classifier & maven2 specific IDs as a facet
372                     //        (should also have been added to the Dependency)
373
374                     // TODO: add a property that is a weak reference to the originating artifact, creating it if
375                     //       necessary (without adding the archiva:artifact mixin so that it doesn't get listed as an
376                     //       artifact, which gives a different meaning to "incomplete" which is a known local project
377                     //       that doesn't have metadata yet but has artifacts). (Though we may want to give it the
378                     //       artifact mixin and another property to identify all non-local artifacts for the closure
379                     //       reports)
380                 }
381             }
382
383             for (MetadataFacet facet : versionMetadata.getFacetList()) {
384                 // recreate, to ensure properties are removed
385                 if (versionNode.hasNode(facet.getFacetId())) {
386                     versionNode.getNode(facet.getFacetId()).remove();
387                 }
388                 Node n = versionNode.addNode(facet.getFacetId(), FACET_NODE_TYPE);
389
390                 for (Map.Entry<String, String> entry : facet.toProperties().entrySet()) {
391                     n.setProperty(entry.getKey(), entry.getValue());
392                 }
393             }
394         } catch (RepositoryException e) {
395             throw new MetadataRepositoryException(e.getMessage(), e);
396         }
397     }
398
399     private void updateNamespace(Session jcrSession, String repositoryId, String namespace) throws MetadataRepositoryException {
400         try {
401             Node node = getOrAddNamespaceNode(jcrSession, repositoryId, namespace);
402             node.setProperty("id", namespace);
403             node.setProperty("namespace", namespace);
404         } catch (RepositoryException e) {
405             throw new MetadataRepositoryException(e.getMessage(), e);
406         }
407     }
408
409     @Override
410     public void updateNamespace(RepositorySession session, String repositoryId, String namespace)
411             throws MetadataRepositoryException {
412         updateNamespace(getSession(session), repositoryId, namespace);
413     }
414
415     @Override
416     public void removeProject(RepositorySession session, String repositoryId, String namespace, String projectId)
417             throws MetadataRepositoryException {
418         final Session jcrSession = getSession(session);
419         try {
420             Node root = jcrSession.getRootNode();
421             String namespacePath = getNamespacePath(repositoryId, namespace);
422
423             if (root.hasNode(namespacePath)) {
424                 Iterator<Node> nodeIterator = JcrUtils.getChildNodes(root.getNode(namespacePath)).iterator();
425                 while (nodeIterator.hasNext()) {
426                     Node node = nodeIterator.next();
427                     if (node.isNodeType(org.apache.archiva.metadata.repository.jcr.JcrConstants.PROJECT_MIXIN_TYPE) && projectId.equals(node.getName())) {
428                         node.remove();
429                     }
430                 }
431
432             }
433         } catch (RepositoryException e) {
434             throw new MetadataRepositoryException(e.getMessage(), e);
435         }
436
437     }
438
439
440     @Override
441     public boolean hasMetadataFacet(RepositorySession session, String repositoryId, String facetId)
442             throws MetadataRepositoryException {
443         final Session jcrSession = getSession(session);
444         try {
445             Node node = jcrSession.getRootNode().getNode(getFacetPath(repositoryId, facetId));
446             return node.getNodes().hasNext();
447         } catch (PathNotFoundException e) {
448             // ignored - the facet doesn't exist, so return false
449             return false;
450         } catch (RepositoryException e) {
451             throw new MetadataRepositoryException(e.getMessage(), e);
452         }
453     }
454
455     @Override
456     public List<String> getMetadataFacets(RepositorySession session, String repositoryId, String facetId)
457             throws MetadataRepositoryException {
458         final Session jcrSession = getSession(session);
459         List<String> facets = new ArrayList<>();
460
461         try {
462             // no need to construct node-by-node here, as we'll find in the next instance, the facet names have / and
463             // are paths themselves
464             Node node = jcrSession.getRootNode().getNode(getFacetPath(repositoryId, facetId));
465
466             // TODO: this is a bit awkward. Might be better to review the purpose of this function - why is the list of
467             //   paths helpful?
468             recurse(facets, "", node);
469         } catch (PathNotFoundException e) {
470             // ignored - the facet doesn't exist, so return the empty list
471         } catch (RepositoryException e) {
472             throw new MetadataRepositoryException(e.getMessage(), e);
473         }
474         return facets;
475     }
476
477     private <T> Spliterator<T> createResultSpliterator(QueryResult result, Function<Row, T> converter) throws MetadataRepositoryException {
478         final RowIterator rowIterator;
479         try {
480             rowIterator = result.getRows();
481         } catch (RepositoryException e) {
482             throw new MetadataRepositoryException(e.getMessage(), e);
483         }
484         return new Spliterator<T>() {
485             @Override
486             public boolean tryAdvance(Consumer<? super T> action) {
487                 while (rowIterator.hasNext()) {
488                     T item = converter.apply(rowIterator.nextRow());
489                     if (item != null) {
490                         action.accept(item);
491                         return true;
492                     }
493                 }
494                 return false;
495             }
496
497             @Override
498             public Spliterator<T> trySplit() {
499                 return null;
500             }
501
502             @Override
503             public long estimateSize() {
504                 return 0;
505             }
506
507             @Override
508             public int characteristics() {
509                 return ORDERED + NONNULL;
510             }
511         };
512     }
513
514     private StringBuilder appendQueryParams(StringBuilder query, String selector, String defaultProperty, QueryParameter queryParameter) {
515         if (queryParameter.getSortFields().size() == 0) {
516             query.append(" ORDER BY [").append(selector).append("].[").append(defaultProperty).append("]");
517             if (queryParameter.isAscending()) {
518                 query.append(" ASC");
519             } else {
520                 query.append(" DESC");
521             }
522         } else {
523             query.append(" ORDER BY");
524             for (String property : queryParameter.getSortFields()) {
525                 query.append(" [").append(selector).append("].[").append(property).append("]");
526                 if (queryParameter.isAscending()) {
527                     query.append(" ASC");
528                 } else {
529                     query.append(" DESC");
530                 }
531             }
532         }
533         return query;
534     }
535
536     private <T extends MetadataFacet> Function<Row, Optional<T>> getFacetFromRowFunc(MetadataFacetFactory<T> factory, String repositoryId) {
537         return (Row row) -> {
538             try {
539                 Node node = row.getNode("facet");
540                 if (node.hasProperty("archiva:name")) {
541                     String facetName = node.getProperty("archiva:name").getString();
542                     return Optional.ofNullable(createFacetFromNode(factory, node, repositoryId, facetName));
543                 } else {
544                     return Optional.empty();
545                 }
546             } catch (RepositoryException e) {
547                 log.error("Exception encountered {}", e.getMessage());
548                 return Optional.empty();
549             }
550         };
551     }
552
553     @Override
554     public <T extends MetadataFacet> Stream<T> getMetadataFacetStream(RepositorySession session, String repositoryId, Class<T> facetClazz, QueryParameter queryParameter) throws MetadataRepositoryException {
555         final Session jcrSession = getSession(session);
556         final MetadataFacetFactory<T> factory = metadataService.getFactory(facetClazz);
557         final String facetId = factory.getFacetId();
558         final String facetPath = '/' + getFacetPath(repositoryId, facetId);
559         StringBuilder query = new StringBuilder("SELECT * FROM [");
560         query.append(FACET_NODE_TYPE).append("] AS facet WHERE ISDESCENDANTNODE(facet, [")
561                 .append(facetPath).append("]) AND [facet].[archiva:name] IS NOT NULL");
562         appendQueryParams(query, "facet", "archiva:name", queryParameter);
563         String q = query.toString();
564         Map<String, String> params = new HashMap<>();
565         QueryResult result = runNativeJcrQuery(jcrSession, q, params, queryParameter.getOffset(), queryParameter.getLimit());
566         final Function<Row, Optional<T>> rowFunc = getFacetFromRowFunc(factory, repositoryId);
567         return StreamSupport.stream(createResultSpliterator(result, rowFunc), false).filter(Optional::isPresent).map(Optional::get);
568
569     }
570
571     private void recurse(List<String> facets, String prefix, Node node)
572             throws RepositoryException {
573         for (Node n : JcrUtils.getChildNodes(node)) {
574             String name = prefix + "/" + n.getName();
575             if (n.hasNodes()) {
576                 recurse(facets, name, n);
577             } else {
578                 // strip leading / first
579                 facets.add(name.substring(1));
580             }
581         }
582     }
583
584
585     @Override
586     public <T extends MetadataFacet> T getMetadataFacet(RepositorySession session, String repositoryId, Class<T> clazz, String name) throws MetadataRepositoryException {
587         if (!metadataService.supportsFacet(clazz)) {
588             log.warn("The required metadata class is not supported: " + clazz);
589             return null;
590         }
591         final Session jcrSession = getSession(session);
592         final MetadataFacetFactory<T> factory = getFacetFactory(clazz);
593         final String facetId = factory.getFacetId();
594         try {
595             Node root = jcrSession.getRootNode();
596             Node node = root.getNode(getFacetPath(repositoryId, facetId, name));
597
598             if (getSupportedFacets().size() == 0) {
599                 return null;
600             }
601
602             return createFacetFromNode(factory, node, repositoryId, name);
603         } catch (PathNotFoundException e) {
604             // ignored - the facet doesn't exist, so return null
605         } catch (RepositoryException e) {
606             throw new MetadataRepositoryException(e.getMessage(), e);
607         }
608         return null;
609     }
610
611     private <T extends MetadataFacet> T createFacetFromNode(final MetadataFacetFactory<T> factory, final Node node) throws RepositoryException {
612         return createFacetFromNode(factory, node, null, null);
613     }
614
615     private <T extends MetadataFacet> T createFacetFromNode(final MetadataFacetFactory<T> factory, final Node node,
616                                                             final String repositoryId, final String name) throws RepositoryException {
617         if (factory != null) {
618             T metadataFacet;
619             if (repositoryId != null) {
620                 metadataFacet = factory.createMetadataFacet(repositoryId, name);
621             } else {
622                 metadataFacet = factory.createMetadataFacet();
623             }
624             Map<String, String> map = new HashMap<>();
625             for (Property property : JcrUtils.getProperties(node)) {
626                 String p = property.getName();
627                 if (!p.startsWith("jcr:")) {
628                     map.put(p, property.getString());
629                 }
630             }
631             metadataFacet.fromProperties(map);
632             return metadataFacet;
633         }
634         return null;
635     }
636
637     @Override
638     public void addMetadataFacet(RepositorySession session, String repositoryId, MetadataFacet metadataFacet)
639             throws MetadataRepositoryException {
640         final Session jcrSession = getSession(session);
641         try {
642             Node repo = getOrAddRepositoryNode(jcrSession, repositoryId);
643             Node facets = JcrUtils.getOrAddNode(repo, "facets", FACETS_FOLDER_TYPE);
644
645             String id = metadataFacet.getFacetId();
646             Node facetNode = JcrUtils.getOrAddNode(facets, id, FACET_ID_CONTAINER_TYPE);
647             if (!facetNode.hasProperty("id")) {
648                 facetNode.setProperty("id", id);
649             }
650
651             Node facetInstance = getOrAddNodeByPath(facetNode, metadataFacet.getName(), FACET_NODE_TYPE, true);
652             if (!facetInstance.hasProperty("archiva:facetId")) {
653                 facetInstance.setProperty("archiva:facetId", id);
654                 facetInstance.setProperty("archiva:name", metadataFacet.getName());
655             }
656
657             for (Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet()) {
658                 facetInstance.setProperty(entry.getKey(), entry.getValue());
659             }
660             session.save();
661         } catch (RepositoryException | MetadataSessionException e) {
662             throw new MetadataRepositoryException(e.getMessage(), e);
663         }
664     }
665
666     @Override
667     public void removeNamespace(RepositorySession session, String repositoryId, String projectId)
668             throws MetadataRepositoryException {
669         final Session jcrSession = getSession(session);
670         try {
671             Node root = jcrSession.getRootNode();
672             String path = getNamespacePath(repositoryId, projectId);
673             if (root.hasNode(path)) {
674                 Node node = root.getNode(path);
675                 if (node.isNodeType(NAMESPACE_MIXIN_TYPE)) {
676                     node.remove();
677                 }
678             }
679         } catch (RepositoryException e) {
680             throw new MetadataRepositoryException(e.getMessage(), e);
681         }
682     }
683
684     @Override
685     public void removeMetadataFacets(RepositorySession session, String repositoryId, String facetId)
686             throws MetadataRepositoryException {
687         final Session jcrSession = getSession(session);
688         try {
689             Node root = jcrSession.getRootNode();
690             String path = getFacetPath(repositoryId, facetId);
691             if (root.hasNode(path)) {
692                 root.getNode(path).remove();
693             }
694         } catch (RepositoryException e) {
695             throw new MetadataRepositoryException(e.getMessage(), e);
696         }
697     }
698
699     @Override
700     public void removeMetadataFacet(RepositorySession session, String repositoryId, String facetId, String name)
701             throws MetadataRepositoryException {
702         final Session jcrSession = getSession(session);
703         try {
704             Node root = jcrSession.getRootNode();
705             String path = getFacetPath(repositoryId, facetId, name);
706             if (root.hasNode(path)) {
707                 Node node = root.getNode(path);
708                 do {
709                     // also remove empty container nodes
710                     Node parent = node.getParent();
711                     node.remove();
712                     node = parent;
713                 }
714                 while (!node.hasNodes());
715             }
716         } catch (RepositoryException e) {
717             throw new MetadataRepositoryException(e.getMessage(), e);
718         }
719     }
720
721     private StringBuilder buildArtifactByDateRangeQuery(String repoId, ZonedDateTime startTime, ZonedDateTime endTime,
722                                                         QueryParameter queryParameter) {
723         StringBuilder q = getArtifactQuery(repoId);
724
725         if (startTime != null) {
726             q.append(" AND [artifact].[whenGathered] >= $start");
727         }
728         if (endTime != null) {
729             q.append(" AND [artifact].[whenGathered] <= $end");
730         }
731         appendQueryParams(q, "artifact", "whenGathered", queryParameter);
732         return q;
733     }
734
735     private QueryResult queryArtifactByDateRange(Session jcrSession, String repositoryId,
736                                                  ZonedDateTime startTime, ZonedDateTime endTime,
737                                                  QueryParameter queryParameter) throws MetadataRepositoryException {
738         String q = buildArtifactByDateRangeQuery(repositoryId, startTime, endTime, queryParameter).toString();
739
740         try {
741             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
742             query.setOffset(queryParameter.getOffset());
743             query.setLimit(queryParameter.getLimit());
744             ValueFactory valueFactory = jcrSession.getValueFactory();
745             if (startTime != null) {
746                 query.bindValue("start", valueFactory.createValue(createCalendar(startTime.withZoneSameInstant(ModelInfo.STORAGE_TZ))));
747             }
748             if (endTime != null) {
749                 query.bindValue("end", valueFactory.createValue(createCalendar(endTime.withZoneSameInstant(ModelInfo.STORAGE_TZ))));
750             }
751             return query.execute();
752         } catch (RepositoryException e) {
753             throw new MetadataRepositoryException(e.getMessage(), e);
754         }
755     }
756
757     @Override
758     public List<ArtifactMetadata> getArtifactsByDateRange(RepositorySession session, String repoId, ZonedDateTime startTime, ZonedDateTime endTime, QueryParameter queryParameter)
759             throws MetadataRepositoryException {
760         final Session jcrSession = getSession(session);
761
762         List<ArtifactMetadata> artifacts;
763         try {
764             QueryResult result = queryArtifactByDateRange(jcrSession, repoId, startTime, endTime, queryParameter);
765
766             artifacts = new ArrayList<>();
767             for (Node n : JcrUtils.getNodes(result)) {
768                 artifacts.add(getArtifactFromNode(repoId, n));
769             }
770         } catch (RepositoryException e) {
771             throw new MetadataRepositoryException(e.getMessage(), e);
772         }
773         return artifacts;
774     }
775
776     private Function<Row, Optional<ArtifactMetadata>> getArtifactFromRowFunc(final String repositoryId) {
777         return (Row row) -> {
778             try {
779                 return Optional.of(getArtifactFromNode(repositoryId, row.getNode("artifact")));
780             } catch (RepositoryException e) {
781                 return Optional.empty();
782             }
783         };
784     }
785
786     @Override
787     public Stream<ArtifactMetadata> getArtifactByDateRangeStream(RepositorySession session, String repositoryId, ZonedDateTime startTime, ZonedDateTime endTime, QueryParameter queryParameter) throws MetadataRepositoryException {
788         final Session jcrSession = getSession(session);
789         final QueryResult result = queryArtifactByDateRange(jcrSession, repositoryId, startTime, endTime, queryParameter);
790         final Function<Row, Optional<ArtifactMetadata>> rowFunc = getArtifactFromRowFunc(repositoryId);
791         return StreamSupport.stream(createResultSpliterator(result, rowFunc), false).filter(Optional::isPresent).map(Optional::get);
792     }
793
794
795     @Override
796     public List<ArtifactMetadata> getArtifactsByChecksum(RepositorySession session, String repositoryId, String checksum)
797             throws MetadataRepositoryException {
798         final Session jcrSession = getSession(session);
799         List<ArtifactMetadata> artifacts;
800
801         String q = getArtifactQuery(repositoryId).append(" AND ([artifact].[checksums/*/value] = $checksum)").toString();
802
803         try {
804             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
805             ValueFactory valueFactory = jcrSession.getValueFactory();
806             query.bindValue("checksum", valueFactory.createValue(checksum));
807             QueryResult result = query.execute();
808
809             artifacts = new ArrayList<>();
810             for (Node n : JcrUtils.getNodes(result)) {
811                 artifacts.add(getArtifactFromNode(repositoryId, n));
812             }
813         } catch (RepositoryException e) {
814             throw new MetadataRepositoryException(e.getMessage(), e);
815         }
816         return artifacts;
817     }
818
819     public List<ArtifactMetadata> runJcrQuery(Session jcrSession, String repositoryId, String q, Map<String, String> bindingParam)
820             throws MetadataRepositoryException {
821         return runJcrQuery(jcrSession, repositoryId, q, bindingParam, true);
822     }
823
824     public List<ArtifactMetadata> runJcrQuery(final Session jcrSession, final String repositoryId, final String qParam,
825                                               final Map<String, String> bindingParam, final boolean checkPath)
826             throws MetadataRepositoryException {
827
828         String q = qParam;
829         List<ArtifactMetadata> artifacts;
830         if (repositoryId != null && checkPath) {
831             q += " AND ISDESCENDANTNODE(artifact,'/" + getRepositoryContentPath(repositoryId) + "')";
832         }
833
834         log.info("Running JCR Query: {}", q);
835
836         try {
837             QueryResult result = runNativeJcrQuery(jcrSession, q, bindingParam);
838             artifacts = new ArrayList<>();
839             RowIterator rows = result.getRows();
840             while (rows.hasNext()) {
841                 Row row = rows.nextRow();
842                 Node node = row.getNode("artifact");
843                 artifacts.add(getArtifactFromNode(repositoryId, node));
844             }
845         } catch (RepositoryException e) {
846             throw new MetadataRepositoryException(e.getMessage(), e);
847         }
848         log.info("Artifacts found {}", artifacts.size());
849         for (ArtifactMetadata meta : artifacts) {
850             log.info("Artifact: " + meta.getVersion() + " " + meta.getFacetList());
851         }
852         return artifacts;
853     }
854
855     public QueryResult runNativeJcrQuery(final Session jcrSession, final String q, final Map<String, String> bindingParam) throws MetadataRepositoryException {
856         return runNativeJcrQuery(jcrSession, q, bindingParam, 0, Long.MAX_VALUE);
857     }
858
859     public QueryResult runNativeJcrQuery(final Session jcrSession, final String q, final Map<String, String> bindingParam, long offset, long maxEntries)
860             throws MetadataRepositoryException {
861         Map<String, String> bindings;
862         if (bindingParam == null) {
863             bindings = new HashMap<>();
864         } else {
865             bindings = bindingParam;
866         }
867
868         try {
869             log.debug("Query: offset={}, limit={}, query={}", offset, maxEntries, q);
870             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
871             query.setLimit(maxEntries);
872             query.setOffset(offset);
873             ValueFactory valueFactory = jcrSession.getValueFactory();
874             for (Entry<String, String> entry : bindings.entrySet()) {
875                 log.debug("Binding: {}={}", entry.getKey(), entry.getValue());
876                 Value value = valueFactory.createValue(entry.getValue());
877                 log.debug("Binding value {}={}", entry.getKey(), value);
878                 query.bindValue(entry.getKey(), value);
879             }
880             long start = System.currentTimeMillis();
881             log.debug("Execute query {}", query);
882             QueryResult result = query.execute();
883             long end = System.currentTimeMillis();
884             log.info("JCR Query ran in {} milliseconds: {}", end - start, q);
885             return result;
886         } catch (RepositoryException e) {
887             throw new MetadataRepositoryException(e.getMessage(), e);
888         }
889     }
890
891     @Override
892     public List<ArtifactMetadata> getArtifactsByProjectVersionFacet(RepositorySession session, String key, String value, String repositoryId)
893             throws MetadataRepositoryException {
894         final Session jcrSession = getSession(session);
895         final String q = new StringBuilder(QUERY_ARTIFACTS_BY_PROJECT_VERSION_1).append(key).append(QUERY_ARTIFACTS_BY_PROJECT_VERSION_2).toString();
896         Map<String, String> parameterMap = new HashMap<>();
897         parameterMap.put("value", value);
898         parameterMap = Collections.unmodifiableMap(parameterMap);
899         return runJcrQuery(jcrSession, repositoryId, q, parameterMap);
900     }
901
902
903     @Override
904     public List<ArtifactMetadata> getArtifactsByAttribute(RepositorySession session, String key, String value, String repositoryId)
905             throws MetadataRepositoryException {
906         final Session jcrSession = getSession(session);
907         final String q = new StringBuilder(QUERY_ARTIFACTS_BY_METADATA_1).append(key).append(QUERY_ARTIFACTS_BY_METADATA_2).toString();
908         Map<String, String> parameterMap = new HashMap<>();
909         parameterMap.put("value", value);
910         parameterMap = Collections.unmodifiableMap(parameterMap);
911         return runJcrQuery(jcrSession, repositoryId, q, parameterMap);
912     }
913
914
915     @Override
916     public List<ArtifactMetadata> getArtifactsByProjectVersionAttribute(RepositorySession session, String key, String value, String repositoryId)
917             throws MetadataRepositoryException {
918         final Session jcrSession = getSession(session);
919         final String q = new StringBuilder(QUERY_ARTIFACTS_BY_PROPERTY_1).append(key).append(QUERY_ARTIFACTS_BY_PROPERTY_2).toString();
920         Map<String, String> parameterMap = new HashMap<>();
921         parameterMap.put("value", value);
922         parameterMap = Collections.unmodifiableMap(parameterMap);
923         return runJcrQuery(jcrSession, repositoryId, q, parameterMap);
924     }
925
926
927     @Override
928     public void removeRepository(RepositorySession session, String repositoryId)
929             throws MetadataRepositoryException {
930         final Session jcrSession = getSession(session);
931         try {
932             Node root = jcrSession.getRootNode();
933             String path = getRepositoryPath(repositoryId);
934             if (root.hasNode(path)) {
935                 root.getNode(path).remove();
936             }
937         } catch (RepositoryException e) {
938             throw new MetadataRepositoryException(e.getMessage(), e);
939         }
940     }
941
942     @Override
943     public List<ArtifactMetadata> getArtifacts(RepositorySession session, String repositoryId)
944             throws MetadataRepositoryException {
945         final Session jcrSession = getSession(session);
946         List<ArtifactMetadata> artifacts;
947
948         String q = getArtifactQuery(repositoryId).toString();
949
950         try {
951             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
952             QueryResult result = query.execute();
953
954             artifacts = new ArrayList<>();
955             for (Node n : JcrUtils.getNodes(result)) {
956                 if (n.isNodeType(ARTIFACT_NODE_TYPE)) {
957                     artifacts.add(getArtifactFromNode(repositoryId, n));
958                 }
959             }
960         } catch (RepositoryException e) {
961             throw new MetadataRepositoryException(e.getMessage(), e);
962         }
963         return artifacts;
964     }
965
966     private static StringBuilder getArtifactQuery(String repositoryId) {
967         return new StringBuilder(QUERY_ARTIFACT_1).append(getRepositoryContentPath(repositoryId)).append(QUERY_ARTIFACT_2);
968     }
969
970     @Override
971     public ProjectMetadata getProject(RepositorySession session, String repositoryId, String namespace, String projectId)
972             throws MetadataResolutionException {
973         final Session jcrSession;
974         try {
975             jcrSession = getSession(session);
976         } catch (MetadataRepositoryException e) {
977             throw new MetadataResolutionException(e.getMessage());
978         }
979         ProjectMetadata metadata = null;
980
981         try {
982             Node root = jcrSession.getRootNode();
983
984             // basically just checking it exists
985             String path = getProjectPath(repositoryId, namespace, projectId);
986             if (root.hasNode(path)) {
987                 metadata = new ProjectMetadata();
988                 metadata.setId(projectId);
989                 metadata.setNamespace(namespace);
990             }
991         } catch (RepositoryException e) {
992             throw new MetadataResolutionException(e.getMessage(), e);
993         }
994
995         return metadata;
996     }
997
998     private static Optional<License> getLicense(Node licenseNode) {
999         try {
1000             String licenseName = licenseNode.getName();
1001             String licenseUrl = getPropertyString(licenseNode, "url");
1002             License license = new License();
1003             license.setName(licenseName);
1004             license.setUrl(licenseUrl);
1005             return Optional.of(license);
1006         } catch (RepositoryException e) {
1007             return Optional.empty();
1008         }
1009     }
1010
1011     private static Optional<MailingList> getMailinglist(Node mailinglistNode) {
1012         try {
1013             String mailingListName = mailinglistNode.getName();
1014         MailingList mailinglist = new MailingList();
1015         mailinglist.setName(mailingListName);
1016         mailinglist.setMainArchiveUrl(getPropertyString(mailinglistNode, "archive"));
1017         String n = "otherArchives";
1018         if (mailinglistNode.hasProperty(n)) {
1019             mailinglist.setOtherArchives(Arrays.asList(getPropertyString(mailinglistNode, n).split(",")));
1020         } else {
1021             mailinglist.setOtherArchives(Collections.<String>emptyList());
1022         }
1023         mailinglist.setPostAddress(getPropertyString(mailinglistNode, "post"));
1024         mailinglist.setSubscribeAddress(getPropertyString(mailinglistNode, "subscribe"));
1025         mailinglist.setUnsubscribeAddress(getPropertyString(mailinglistNode, "unsubscribe"));
1026         return Optional.of(mailinglist);
1027         } catch (RepositoryException e) {
1028             return Optional.empty();
1029         }
1030
1031     }
1032
1033     @Override
1034     public ProjectVersionMetadata getProjectVersion(RepositorySession session, String repositoryId, String namespace, String projectId,
1035                                                     String projectVersion)
1036             throws MetadataResolutionException {
1037         final Session jcrSession;
1038         try {
1039             jcrSession = getSession(session);
1040         } catch (MetadataRepositoryException e) {
1041             throw new MetadataResolutionException(e.getMessage());
1042         }
1043         ProjectVersionMetadata versionMetadata;
1044
1045         try {
1046             Node root = jcrSession.getRootNode();
1047
1048             String path = getProjectVersionPath(repositoryId, namespace, projectId, projectVersion);
1049             if (!root.hasNode(path)) {
1050                 return null;
1051             }
1052
1053             Node node = root.getNode(path);
1054
1055             versionMetadata = new ProjectVersionMetadata();
1056             versionMetadata.setId(projectVersion);
1057             versionMetadata.setName(getPropertyString(node, "name"));
1058             versionMetadata.setDescription(getPropertyString(node, "description"));
1059             versionMetadata.setUrl(getPropertyString(node, "url"));
1060             versionMetadata.setIncomplete(
1061                     node.hasProperty("incomplete") && node.getProperty("incomplete").getBoolean());
1062
1063             // FIXME: decide how to treat these in the content repo
1064             String scmConnection = getPropertyString(node, "scm.connection");
1065             String scmDeveloperConnection = getPropertyString(node, "scm.developerConnection");
1066             String scmUrl = getPropertyString(node, "scm.url");
1067             if (scmConnection != null || scmDeveloperConnection != null || scmUrl != null) {
1068                 Scm scm = new Scm();
1069                 scm.setConnection(scmConnection);
1070                 scm.setDeveloperConnection(scmDeveloperConnection);
1071                 scm.setUrl(scmUrl);
1072                 versionMetadata.setScm(scm);
1073             }
1074
1075             String ciSystem = getPropertyString(node, "ci.system");
1076             String ciUrl = getPropertyString(node, "ci.url");
1077             if (ciSystem != null || ciUrl != null) {
1078                 CiManagement ci = new CiManagement();
1079                 ci.setSystem(ciSystem);
1080                 ci.setUrl(ciUrl);
1081                 versionMetadata.setCiManagement(ci);
1082             }
1083
1084             String issueSystem = getPropertyString(node, "issue.system");
1085             String issueUrl = getPropertyString(node, "issue.url");
1086             if (issueSystem != null || issueUrl != null) {
1087                 IssueManagement issueManagement = new IssueManagement();
1088                 issueManagement.setSystem(issueSystem);
1089                 issueManagement.setUrl(issueUrl);
1090                 versionMetadata.setIssueManagement(issueManagement);
1091             }
1092
1093             String orgName = getPropertyString(node, "org.name");
1094             String orgUrl = getPropertyString(node, "org.url");
1095             if (orgName != null || orgUrl != null) {
1096                 Organization org = new Organization();
1097                 org.setName(orgName);
1098                 org.setUrl(orgUrl);
1099                 versionMetadata.setOrganization(org);
1100             }
1101
1102             if (node.hasNode("licenses")) {
1103                 Node licensesListNode = node.getNode("licenses");
1104                 List<License> licenseList = StreamSupport.stream(JcrUtils.getChildNodes(licensesListNode).spliterator(),false)
1105                         .map(JcrMetadataRepository::getLicense).filter(Optional::isPresent)
1106                         .map(Optional::get).sorted().collect(Collectors.toList());
1107                 versionMetadata.setLicenses(licenseList);
1108             }
1109             if (node.hasNode("mailinglists")) {
1110                 Node mailinglistsListNode = node.getNode("mailinglists");
1111                 List<MailingList> mailinglistList = StreamSupport.stream(JcrUtils.getChildNodes(mailinglistsListNode).spliterator(), false)
1112                         .map(JcrMetadataRepository::getMailinglist)
1113                         .filter(Optional::isPresent)
1114                         .map(Optional::get)
1115                         .sorted().collect(Collectors.toList());
1116                 versionMetadata.setMailingLists(mailinglistList);
1117             }
1118
1119             if (node.hasNode("dependencies")) {
1120                 Node dependenciesNode = node.getNode("dependencies");
1121                 for (Node n : JcrUtils.getChildNodes(dependenciesNode)) {
1122                     if (n.isNodeType(DEPENDENCY_NODE_TYPE)) {
1123                         Dependency dependency = new Dependency();
1124                         // FIXME: correct these properties
1125                         dependency.setNamespace(getPropertyString(n, "namespace"));
1126                         dependency.setProjectId(getPropertyString(n, "projectId"));
1127                         dependency.setVersion(getPropertyString(n, "version"));
1128                         dependency.setArtifactId(getPropertyString(n, "artifactId"));
1129                         dependency.setClassifier(getPropertyString(n, "classifier"));
1130                         dependency.setOptional(Boolean.valueOf(getPropertyString(n, "optional")));
1131                         dependency.setScope(getPropertyString(n, "scope"));
1132                         dependency.setSystemPath(getPropertyString(n, "systemPath"));
1133                         dependency.setType(getPropertyString(n, "type"));
1134                         versionMetadata.addDependency(dependency);
1135                     }
1136                 }
1137             }
1138
1139             retrieveFacetProperties(versionMetadata, node);
1140         } catch (RepositoryException e) {
1141             throw new MetadataResolutionException(e.getMessage(), e);
1142         }
1143
1144         return versionMetadata;
1145     }
1146
1147     private void retrieveFacetProperties(FacetedMetadata metadata, Node node) throws RepositoryException {
1148         for (Node n : JcrUtils.getChildNodes(node)) {
1149             if (n.isNodeType(FACET_NODE_TYPE)) {
1150                 String name = n.getName();
1151                 MetadataFacetFactory factory = metadataService.getFactory(name);
1152                 if (factory == null) {
1153                     log.error("Attempted to load unknown project version metadata facet: {}", name);
1154                 } else {
1155                     MetadataFacet facet = createFacetFromNode(factory, n);
1156                     metadata.addFacet(facet);
1157                 }
1158             }
1159         }
1160     }
1161
1162     @Override
1163     public List<String> getArtifactVersions(RepositorySession session, String repositoryId, String namespace, String projectId,
1164                                             String projectVersion)
1165             throws MetadataResolutionException {
1166         final Session jcrSession;
1167         try {
1168             jcrSession = getSession(session);
1169         } catch (MetadataRepositoryException e) {
1170             throw new MetadataResolutionException(e.getMessage());
1171         }
1172         Set<String> versions = new LinkedHashSet<String>();
1173
1174         try {
1175             Node root = jcrSession.getRootNode();
1176
1177             Node node = root.getNode(getProjectVersionPath(repositoryId, namespace, projectId, projectVersion));
1178
1179             for (Node n : JcrUtils.getChildNodes(node)) {
1180                 versions.add(n.getProperty("version").getString());
1181             }
1182         } catch (PathNotFoundException e) {
1183             // ignore repo not found for now
1184         } catch (RepositoryException e) {
1185             throw new MetadataResolutionException(e.getMessage(), e);
1186         }
1187
1188         return new ArrayList<>(versions);
1189     }
1190
1191     @Override
1192     public List<ProjectVersionReference> getProjectReferences(RepositorySession session, String repositoryId, String namespace,
1193                                                               String projectId, String projectVersion)
1194             throws MetadataResolutionException {
1195         final Session jcrSession;
1196         try {
1197             jcrSession = getSession(session);
1198         } catch (MetadataRepositoryException e) {
1199             throw new MetadataResolutionException(e.getMessage());
1200         }
1201
1202         List<ProjectVersionReference> references = new ArrayList<>();
1203
1204         // TODO: bind variables instead
1205         String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId
1206                 + "/content]) AND [namespace]='" + namespace + "' AND [artifactId]='" + projectId + "'";
1207         if (projectVersion != null) {
1208             q += " AND [version]='" + projectVersion + "'";
1209         }
1210         try {
1211             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
1212             QueryResult result = query.execute();
1213
1214             for (Node n : JcrUtils.getNodes(result)) {
1215                 n = n.getParent(); // dependencies grouping element
1216
1217                 n = n.getParent(); // project version
1218                 String usedByProjectVersion = n.getName();
1219
1220                 n = n.getParent(); // project
1221                 String usedByProject = n.getName();
1222
1223                 n = n.getParent(); // namespace
1224                 String usedByNamespace = n.getProperty("namespace").getString();
1225
1226                 ProjectVersionReference ref = new ProjectVersionReference();
1227                 ref.setNamespace(usedByNamespace);
1228                 ref.setProjectId(usedByProject);
1229                 ref.setProjectVersion(usedByProjectVersion);
1230                 ref.setReferenceType(ProjectVersionReference.ReferenceType.DEPENDENCY);
1231                 references.add(ref);
1232             }
1233         } catch (RepositoryException e) {
1234             throw new MetadataResolutionException(e.getMessage(), e);
1235         }
1236
1237         return references;
1238     }
1239
1240     @Override
1241     public List<String> getRootNamespaces(RepositorySession session, String repositoryId)
1242             throws MetadataResolutionException {
1243         return this.getChildNamespaces(session, repositoryId, null);
1244     }
1245
1246     @Override
1247     public List<String> getChildNamespaces(RepositorySession session, String repositoryId, String baseNamespace)
1248             throws MetadataResolutionException {
1249         String path = baseNamespace != null
1250                 ? getNamespacePath(repositoryId, baseNamespace)
1251                 : getRepositoryContentPath(repositoryId);
1252
1253         try {
1254             return getNodeNames(getSession(session), path, NAMESPACE_MIXIN_TYPE);
1255         } catch (MetadataRepositoryException e) {
1256             throw new MetadataResolutionException(e.getMessage());
1257         }
1258     }
1259
1260     @Override
1261     public List<String> getProjects(RepositorySession session, String repositoryId, String namespace)
1262             throws MetadataResolutionException {
1263         try {
1264             return getNodeNames(getSession(session), getNamespacePath(repositoryId, namespace), org.apache.archiva.metadata.repository.jcr.JcrConstants.PROJECT_MIXIN_TYPE);
1265         } catch (MetadataRepositoryException e) {
1266             throw new MetadataResolutionException(e.getMessage());
1267         }
1268     }
1269
1270     @Override
1271     public List<String> getProjectVersions(RepositorySession session, String repositoryId, String namespace, String projectId)
1272             throws MetadataResolutionException {
1273         try {
1274             return getNodeNames(getSession(session), getProjectPath(repositoryId, namespace, projectId), PROJECT_VERSION_NODE_TYPE);
1275         } catch (MetadataRepositoryException e) {
1276             throw new MetadataResolutionException(e.getMessage());
1277         }
1278     }
1279
1280     @Override
1281     public void removeTimestampedArtifact(RepositorySession session, ArtifactMetadata artifactMetadata, String baseVersion)
1282             throws MetadataRepositoryException {
1283         final Session jcrSession = getSession(session);
1284         String repositoryId = artifactMetadata.getRepositoryId();
1285
1286         try {
1287             Node root = jcrSession.getRootNode();
1288             String path =
1289                     getProjectVersionPath(repositoryId, artifactMetadata.getNamespace(), artifactMetadata.getProject(),
1290                             baseVersion);
1291
1292             if (root.hasNode(path)) {
1293                 Node node = root.getNode(path);
1294
1295                 for (Node n : JcrUtils.getChildNodes(node)) {
1296                     if (n.isNodeType(ARTIFACT_NODE_TYPE)) {
1297                         if (n.hasProperty("version")) {
1298                             String version = n.getProperty("version").getString();
1299                             if (StringUtils.equals(version, artifactMetadata.getVersion())) {
1300                                 n.remove();
1301                             }
1302                         }
1303
1304                     }
1305                 }
1306             }
1307         } catch (RepositoryException e) {
1308             throw new MetadataRepositoryException(e.getMessage(), e);
1309         }
1310
1311
1312     }
1313
1314
1315     @Override
1316     public void removeProjectVersion(RepositorySession session, String repoId, String namespace, String projectId, String projectVersion)
1317             throws MetadataRepositoryException {
1318         final Session jcrSession = getSession(session);
1319         try {
1320
1321             String path = getProjectPath(repoId, namespace, projectId);
1322             Node root = jcrSession.getRootNode();
1323
1324             Node nodeAtPath = root.getNode(path);
1325
1326             for (Node node : JcrUtils.getChildNodes(nodeAtPath)) {
1327                 if (node.isNodeType(PROJECT_VERSION_NODE_TYPE) && StringUtils.equals(projectVersion,
1328                         node.getName())) {
1329                     node.remove();
1330                 }
1331             }
1332         } catch (RepositoryException e) {
1333             throw new MetadataRepositoryException(e.getMessage(), e);
1334         }
1335     }
1336
1337     @Override
1338     public void removeArtifact(RepositorySession session, String repositoryId, String namespace, String projectId, String projectVersion,
1339                                String id)
1340             throws MetadataRepositoryException {
1341         final Session jcrSession = getSession(session);
1342         try {
1343             Node root = jcrSession.getRootNode();
1344             String path = getArtifactPath(repositoryId, namespace, projectId, projectVersion, id);
1345             if (root.hasNode(path)) {
1346                 root.getNode(path).remove();
1347             }
1348
1349             // remove version
1350
1351             path = getProjectPath(repositoryId, namespace, projectId);
1352
1353             Node nodeAtPath = root.getNode(path);
1354
1355             for (Node node : JcrUtils.getChildNodes(nodeAtPath)) {
1356                 if (node.isNodeType(PROJECT_VERSION_NODE_TYPE) //
1357                         && StringUtils.equals(node.getName(), projectVersion)) {
1358                     node.remove();
1359                 }
1360             }
1361         } catch (RepositoryException e) {
1362             throw new MetadataRepositoryException(e.getMessage(), e);
1363         }
1364     }
1365
1366     @Override
1367     public void removeFacetFromArtifact(RepositorySession session, String repositoryId, String namespace, String project, String projectVersion,
1368                                         MetadataFacet metadataFacet)
1369             throws MetadataRepositoryException {
1370         final Session jcrSession = getSession(session);
1371         try {
1372             Node root = jcrSession.getRootNode();
1373             String path = getProjectVersionPath(repositoryId, namespace, project, projectVersion);
1374
1375             if (root.hasNode(path)) {
1376                 Node node = root.getNode(path);
1377
1378                 for (Node n : JcrUtils.getChildNodes(node)) {
1379                     if (n.isNodeType(ARTIFACT_NODE_TYPE)) {
1380                         ArtifactMetadata artifactMetadata = getArtifactFromNode(repositoryId, n);
1381                         log.debug("artifactMetadata: {}", artifactMetadata);
1382                         MetadataFacet metadataFacetToRemove = artifactMetadata.getFacet(metadataFacet.getFacetId());
1383                         if (metadataFacetToRemove != null && metadataFacet.equals(metadataFacetToRemove)) {
1384                             n.remove();
1385                         }
1386                     }
1387                 }
1388             }
1389         } catch (RepositoryException e) {
1390             throw new MetadataRepositoryException(e.getMessage(), e);
1391         }
1392     }
1393
1394     @Override
1395     public List<ArtifactMetadata> getArtifacts(RepositorySession session, String repositoryId, String namespace, String projectId,
1396                                                String projectVersion)
1397             throws MetadataResolutionException {
1398         final Session jcrSession;
1399         try {
1400             jcrSession = getSession(session);
1401         } catch (MetadataRepositoryException e) {
1402             throw new MetadataResolutionException(e.getMessage());
1403         }
1404         List<ArtifactMetadata> artifacts = new ArrayList<>();
1405
1406         try {
1407             Node root = jcrSession.getRootNode();
1408             String path = getProjectVersionPath(repositoryId, namespace, projectId, projectVersion);
1409
1410             if (root.hasNode(path)) {
1411                 Node node = root.getNode(path);
1412
1413                 for (Node n : JcrUtils.getChildNodes(node)) {
1414                     if (n.isNodeType(ARTIFACT_NODE_TYPE)) {
1415                         artifacts.add(getArtifactFromNode(repositoryId, n));
1416                     }
1417                 }
1418             }
1419         } catch (RepositoryException e) {
1420             throw new MetadataResolutionException(e.getMessage(), e);
1421         }
1422
1423         return artifacts;
1424     }
1425
1426
1427     @Override
1428     public void close()
1429             throws MetadataRepositoryException {
1430     }
1431
1432
1433     /**
1434      * Exact is ignored as we can't do exact search in any property, we need a key
1435      */
1436     @Override
1437     public List<ArtifactMetadata> searchArtifacts(RepositorySession session, String repositoryId, String text, boolean exact)
1438             throws MetadataRepositoryException {
1439         return searchArtifacts(session, repositoryId, null, text, exact);
1440     }
1441
1442     @Override
1443     public List<ArtifactMetadata> searchArtifacts(RepositorySession session, String repositoryId, String key, String text, boolean exact)
1444             throws MetadataRepositoryException {
1445         final Session jcrSession = getSession(session);
1446         String theKey = key == null ? "*" : "[" + key + "]";
1447         String projectVersionCondition =
1448                 exact ? "(projectVersion." + theKey + " = $value)" : "contains([projectVersion]." + theKey + ", $value)";
1449         String facetCondition = exact ? "(facet." + theKey + " = $value)" : "contains([facet]." + theKey + ", $value)";
1450         String descendantCondition = repositoryId == null ?
1451                 " AND [projectVersion].[jcr:path] LIKE '/repositories/%/content/%'" :
1452                 " AND ISDESCENDANTNODE(projectVersion,'/" + getRepositoryContentPath(repositoryId) + "')";
1453         List<ArtifactMetadata> result = new ArrayList<>();
1454         if (key == null || (key != null && Arrays.binarySearch(PROJECT_VERSION_VERSION_PROPERTIES, key) >= 0)) {
1455             // We search only for project version properties if the key is a valid property name
1456             String q1 =
1457                     "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE
1458                             + "] AS projectVersion LEFT OUTER JOIN [" + ARTIFACT_NODE_TYPE
1459                             + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE " + projectVersionCondition + descendantCondition;
1460             Map<String, String> parameterMap = new HashMap<>();
1461             parameterMap.put("value", text);
1462             parameterMap = Collections.unmodifiableMap(parameterMap);
1463             result.addAll(runJcrQuery(jcrSession, repositoryId, q1, parameterMap, false));
1464         }
1465         String q2 =
1466                 "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE
1467                         + "] AS projectVersion LEFT OUTER JOIN [" + ARTIFACT_NODE_TYPE
1468                         + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) LEFT OUTER JOIN [" + FACET_NODE_TYPE
1469                         + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE " + facetCondition + descendantCondition;
1470         Map<String, String> parameterMap = new HashMap<>();
1471         parameterMap.put("value", text);
1472         parameterMap = Collections.unmodifiableMap(parameterMap);
1473         result.addAll(runJcrQuery(jcrSession, repositoryId, q2, parameterMap, false));
1474         return result;
1475     }
1476
1477     private ArtifactMetadata getArtifactFromNode(String repositoryId, Node artifactNode)
1478             throws RepositoryException {
1479         String id = artifactNode.getName();
1480
1481         ArtifactMetadata artifact = new ArtifactMetadata();
1482         artifact.setId(id);
1483         artifact.setRepositoryId(repositoryId == null ? artifactNode.getAncestor(2).getName() : repositoryId);
1484
1485         Node projectVersionNode = artifactNode.getParent();
1486         Node projectNode = projectVersionNode.getParent();
1487         Node namespaceNode = projectNode.getParent();
1488
1489         artifact.setNamespace(namespaceNode.getProperty("namespace").getString());
1490         artifact.setProject(projectNode.getName());
1491         artifact.setProjectVersion(projectVersionNode.getName());
1492         artifact.setVersion(artifactNode.hasProperty("version")
1493                 ? artifactNode.getProperty("version").getString()
1494                 : projectVersionNode.getName());
1495
1496         if (artifactNode.hasProperty(JCR_LAST_MODIFIED)) {
1497             artifact.setFileLastModified(artifactNode.getProperty(JCR_LAST_MODIFIED).getDate().getTimeInMillis());
1498         }
1499
1500         if (artifactNode.hasProperty("whenGathered")) {
1501             Calendar cal = artifactNode.getProperty("whenGathered").getDate();
1502             artifact.setWhenGathered(ZonedDateTime.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId()));
1503         }
1504
1505         if (artifactNode.hasProperty("size")) {
1506             artifact.setSize(artifactNode.getProperty("size").getLong());
1507         }
1508
1509         Node cslistNode = getOrAddNodeByPath(artifactNode, "checksums");
1510         NodeIterator csNodeIt = cslistNode.getNodes("*");
1511         while (csNodeIt.hasNext()) {
1512             Node csNode = csNodeIt.nextNode();
1513             if (csNode.isNodeType(CHECKSUM_NODE_TYPE)) {
1514                 addChecksum(artifact, csNode);
1515             }
1516         }
1517
1518         retrieveFacetProperties(artifact, artifactNode);
1519         return artifact;
1520     }
1521
1522     private void addChecksum(ArtifactMetadata artifact, Node n) {
1523         try {
1524             ChecksumAlgorithm alg = ChecksumAlgorithm.valueOf(n.getProperty("type").getString());
1525             String value = n.getProperty("value").getString();
1526             artifact.setChecksum(alg, value);
1527         } catch (Throwable e) {
1528             log.error("Could not set checksum from node {}", n);
1529         }
1530     }
1531
1532     private static String getPropertyString(Node node, String name)
1533             throws RepositoryException {
1534         return node.hasProperty(name) ? node.getProperty(name).getString() : null;
1535     }
1536
1537     private List<String> getNodeNames(Session jcrSession, String path, String nodeType)
1538             throws MetadataResolutionException {
1539
1540         List<String> names = new ArrayList<>();
1541
1542         try {
1543             Node root = jcrSession.getRootNode();
1544
1545             Node nodeAtPath = root.getNode(path);
1546
1547             for (Node node : JcrUtils.getChildNodes(nodeAtPath)) {
1548                 if (node.isNodeType(nodeType)) {
1549                     names.add(node.getName());
1550                 }
1551             }
1552         } catch (PathNotFoundException e) {
1553             // ignore repo not found for now
1554         } catch (RepositoryException e) {
1555             throw new MetadataResolutionException(e.getMessage(), e);
1556         }
1557
1558         return names;
1559     }
1560
1561     private static String getRepositoryPath(String repositoryId) {
1562         return "repositories/" + repositoryId;
1563     }
1564
1565     private static String getRepositoryContentPath(String repositoryId) {
1566         return getRepositoryPath(repositoryId) + "/content";
1567     }
1568
1569     private static String getFacetPath(String repositoryId, String facetId) {
1570         return StringUtils.isEmpty(facetId) ? getRepositoryPath(repositoryId) + "/facets" :
1571                 getRepositoryPath(repositoryId) + "/facets/" + facetId;
1572     }
1573
1574     private static String getNamespacePath(String repositoryId, String namespace) {
1575         return getRepositoryContentPath(repositoryId) + "/" + namespace.replace('.', '/');
1576     }
1577
1578     private static String getProjectPath(String repositoryId, String namespace, String projectId) {
1579         return getNamespacePath(repositoryId, namespace) + "/" + projectId;
1580     }
1581
1582     private static String getProjectVersionPath(String repositoryId, String namespace, String projectId,
1583                                                 String projectVersion) {
1584         return getProjectPath(repositoryId, namespace, projectId) + "/" + projectVersion;
1585     }
1586
1587     private static String getArtifactPath(String repositoryId, String namespace, String projectId,
1588                                           String projectVersion, String id) {
1589         return getProjectVersionPath(repositoryId, namespace, projectId, projectVersion) + "/" + id;
1590     }
1591
1592     private Node getOrAddNodeByPath(Node baseNode, String name)
1593             throws RepositoryException {
1594         return getOrAddNodeByPath(baseNode, name, null);
1595     }
1596
1597     private Node getOrAddNodeByPath(Node baseNode, String name, String nodeType) throws RepositoryException {
1598         return getOrAddNodeByPath(baseNode, name, nodeType, false);
1599     }
1600
1601     private Node getOrAddNodeByPath(Node baseNode, String name, String nodeType, boolean primaryType)
1602             throws RepositoryException {
1603         log.debug("getOrAddNodeByPath " + baseNode + " " + name + " " + nodeType);
1604         Node node = baseNode;
1605         for (String n : name.split("/")) {
1606             if (nodeType != null && primaryType) {
1607                 node = JcrUtils.getOrAddNode(node, n, nodeType);
1608             } else {
1609                 node = JcrUtils.getOrAddNode(node, n);
1610                 if (nodeType != null && !node.isNodeType(nodeType)) {
1611                     node.addMixin(nodeType);
1612                 }
1613             }
1614             if (!node.hasProperty("id")) {
1615                 node.setProperty("id", n);
1616             }
1617         }
1618         return node;
1619     }
1620
1621     private Node getOrAddNodeByPath(Node baseNode, String name, String primaryType, String... mixinTypes)
1622             throws RepositoryException {
1623         log.debug("getOrAddNodeByPath baseNode={}, name={}, primary={}, mixin={}", baseNode, name, primaryType, mixinTypes);
1624         Node node = baseNode;
1625         for (String n : name.split("/")) {
1626             node = JcrUtils.getOrAddNode(node, n, primaryType);
1627             for (String mixin : mixinTypes) {
1628                 if (mixin != null && !node.isNodeType(mixin)) {
1629                     node.addMixin(mixin);
1630                 }
1631
1632             }
1633             if (!node.hasProperty("id")) {
1634                 node.setProperty("id", n);
1635             }
1636         }
1637         return node;
1638     }
1639
1640     private static String getFacetPath(String repositoryId, String facetId, String name) {
1641         return getFacetPath(repositoryId, facetId) + "/" + name;
1642     }
1643
1644     private Node getOrAddRepositoryNode(Session jcrSession, String repositoryId)
1645             throws RepositoryException {
1646         log.debug("getOrAddRepositoryNode " + repositoryId);
1647         Node root = jcrSession.getRootNode();
1648         Node node = JcrUtils.getOrAddNode(root, "repositories");
1649         log.debug("Repositories " + node);
1650         node = JcrUtils.getOrAddNode(node, repositoryId, REPOSITORY_NODE_TYPE);
1651         if (!node.hasProperty("id")) {
1652             node.setProperty("id", repositoryId);
1653         }
1654         return node;
1655     }
1656
1657     private Node getOrAddRepositoryContentNode(Session jcrSession, String repositoryId)
1658             throws RepositoryException {
1659         Node node = getOrAddRepositoryNode(jcrSession, repositoryId);
1660         return JcrUtils.getOrAddNode(node, "content", CONTENT_NODE_TYPE);
1661     }
1662
1663     private Node getOrAddNamespaceNode(Session jcrSession, String repositoryId, String namespace)
1664             throws RepositoryException {
1665         Node repo = getOrAddRepositoryContentNode(jcrSession, repositoryId);
1666         return getOrAddNodeByPath(repo, namespace.replace('.', '/'), FOLDER_TYPE, NAMESPACE_MIXIN_TYPE);
1667     }
1668
1669     private Node getOrAddProjectNode(Session jcrSession, String repositoryId, String namespace, String projectId)
1670             throws RepositoryException {
1671         Node namespaceNode = getOrAddNamespaceNode(jcrSession, repositoryId, namespace);
1672         Node node = JcrUtils.getOrAddNode(namespaceNode, projectId, FOLDER_TYPE);
1673         if (!node.isNodeType(PROJECT_MIXIN_TYPE)) {
1674             node.addMixin(PROJECT_MIXIN_TYPE);
1675         }
1676         if (!node.hasProperty("id")) {
1677             node.setProperty("id", projectId);
1678         }
1679         return node;
1680     }
1681
1682     private Node getOrAddProjectVersionNode(Session jcrSession, String repositoryId, String namespace, String projectId,
1683                                             String projectVersion)
1684             throws RepositoryException {
1685         Node projectNode = getOrAddProjectNode(jcrSession, repositoryId, namespace, projectId);
1686         log.debug("Project node {}", projectNode);
1687         Node projectVersionNode = JcrUtils.getOrAddNode(projectNode, projectVersion, PROJECT_VERSION_NODE_TYPE);
1688         if (!projectVersionNode.hasProperty("id")) {
1689             projectVersionNode.setProperty("id", projectVersion);
1690         }
1691
1692         log.debug("Project version node {}", projectVersionNode);
1693         return projectVersionNode;
1694     }
1695
1696     private Node getOrAddArtifactNode(Session jcrSession, String repositoryId, String namespace, String projectId, String projectVersion,
1697                                       String id)
1698             throws RepositoryException {
1699         Node versionNode = getOrAddProjectVersionNode(jcrSession, repositoryId, namespace, projectId, projectVersion);
1700         Node node = JcrUtils.getOrAddNode(versionNode, id, ARTIFACT_NODE_TYPE);
1701         if (!node.hasProperty("id")) {
1702             node.setProperty("id", id);
1703         }
1704         return node;
1705     }
1706
1707     private Node findArtifactNode(Session jcrSession, String namespace, String projectId,
1708                                   String projectVersion, String id) throws RepositoryException {
1709
1710         if (namespace==null || projectId==null||projectVersion==null||id==null) {
1711             return null;
1712         }
1713         Node root = jcrSession.getRootNode();
1714         Node node = JcrUtils.getOrAddNode(root, "repositories");
1715         for (Node n : JcrUtils.getChildNodes(node)) {
1716             String repositoryId = n.getName();
1717             Node repo = getOrAddRepositoryContentNode(jcrSession, repositoryId);
1718             Node nsNode = JcrUtils.getNodeIfExists(repo, StringUtils.replaceChars(namespace, '.', '/'));
1719             if (nsNode!=null) {
1720                 Node projNode = JcrUtils.getNodeIfExists(nsNode, projectId);
1721                 if (projNode !=null ) {
1722                     Node projVersionNode = JcrUtils.getNodeIfExists(projNode, projectVersion);
1723                     if (projVersionNode != null) {
1724                         return JcrUtils.getNodeIfExists(projVersionNode, id);
1725                     }
1726                 }
1727             }
1728         }
1729
1730         return null;
1731     }
1732
1733     private static Calendar createCalendar(ZonedDateTime time) {
1734         return GregorianCalendar.from(time);
1735     }
1736
1737     private String join(Collection<String> ids) {
1738         if (ids != null && !ids.isEmpty()) {
1739             StringBuilder s = new StringBuilder();
1740             for (String id : ids) {
1741                 s.append(id);
1742                 s.append(",");
1743             }
1744             return s.substring(0, s.length() - 1);
1745         }
1746         return null;
1747     }
1748
1749
1750     @Override
1751     public void populateStatistics(RepositorySession repositorySession, MetadataRepository repository, String repositoryId,
1752                                    RepositoryStatistics repositoryStatistics)
1753             throws MetadataRepositoryException {
1754         if (!(repository instanceof JcrMetadataRepository)) {
1755             throw new MetadataRepositoryException(
1756                     "The statistics population is only possible for JcrMetdataRepository implementations");
1757         }
1758         Session session = getSession(repositorySession);
1759         // TODO: these may be best as running totals, maintained by observations on the properties in JCR
1760
1761         try {
1762             QueryManager queryManager = session.getWorkspace().getQueryManager();
1763
1764             // TODO: Check, if this is still the case - Switched to Jackrabbit OAK with archiva 3.0
1765             // Former statement: JCR-SQL2 query will not complete on a large repo in Jackrabbit 2.2.0 - see JCR-2835
1766             //    Using the JCR-SQL2 variants gives
1767             //      "org.apache.lucene.search.BooleanQuery$TooManyClauses: maxClauseCount is set to 1024"
1768 //            String whereClause = "WHERE ISDESCENDANTNODE([/repositories/" + repositoryId + "/content])";
1769 //            Query query = queryManager.createQuery( "SELECT size FROM [archiva:artifact] " + whereClause,
1770 //                                                    Query.JCR_SQL2 );
1771             String whereClause = "WHERE ISDESCENDANTNODE([/repositories/" + repositoryId + "/content])";
1772             Query query = queryManager.createQuery("SELECT type,size FROM [" + ARTIFACT_NODE_TYPE + "] " + whereClause, Query.JCR_SQL2);
1773
1774             QueryResult queryResult = query.execute();
1775
1776             Map<String, Integer> totalByType = new HashMap<>();
1777             long totalSize = 0, totalArtifacts = 0;
1778             for (Row row : JcrUtils.getRows(queryResult)) {
1779                 Node n = row.getNode();
1780                 log.debug("Result node {}", n);
1781                 totalSize += row.getValue("size").getLong();
1782
1783                 String type;
1784                 if (n.hasNode(MavenArtifactFacet.FACET_ID)) {
1785                     Node facetNode = n.getNode(MavenArtifactFacet.FACET_ID);
1786                     type = facetNode.getProperty("type").getString();
1787                 } else {
1788                     type = "Other";
1789                 }
1790                 Integer prev = totalByType.get(type);
1791                 totalByType.put(type, prev != null ? prev + 1 : 1);
1792
1793                 totalArtifacts++;
1794             }
1795
1796             repositoryStatistics.setTotalArtifactCount(totalArtifacts);
1797             repositoryStatistics.setTotalArtifactFileSize(totalSize);
1798             for (Map.Entry<String, Integer> entry : totalByType.entrySet()) {
1799                 log.info("Setting count for type: {} = {}", entry.getKey(), entry.getValue());
1800                 repositoryStatistics.setTotalCountForType(entry.getKey(), entry.getValue());
1801             }
1802
1803             // The query ordering is a trick to ensure that the size is correct, otherwise due to lazy init it will be -1
1804 //            query = queryManager.createQuery( "SELECT * FROM [archiva:project] " + whereClause, Query.JCR_SQL2 );
1805             query = queryManager.createQuery("SELECT * FROM [archiva:project] " + whereClause + " ORDER BY [jcr:score]",
1806                     Query.JCR_SQL2);
1807             repositoryStatistics.setTotalProjectCount(query.execute().getRows().getSize());
1808
1809 //            query = queryManager.createQuery(
1810 //                "SELECT * FROM [archiva:namespace] " + whereClause + " AND namespace IS NOT NULL", Query.JCR_SQL2 );
1811             query = queryManager.createQuery(
1812                     "SELECT * FROM [archiva:namespace] " + whereClause + " AND namespace IS NOT NULL ORDER BY [jcr:score]",
1813                     Query.JCR_SQL2);
1814             repositoryStatistics.setTotalGroupCount(query.execute().getRows().getSize());
1815         } catch (RepositoryException e) {
1816             throw new MetadataRepositoryException(e.getMessage(), e);
1817         }
1818     }
1819
1820
1821     public Session login() throws RepositoryException {
1822         return repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
1823     }
1824
1825     private static boolean isArtifactNodeType(Node n) {
1826         try {
1827             return n != null && n.isNodeType(ARTIFACT_NODE_TYPE);
1828         } catch (RepositoryException e) {
1829             return false;
1830         }
1831     }
1832
1833     private Optional<ArtifactMetadata> getArtifactOptional(final String repositoryId, final Node n) {
1834         try {
1835             return Optional.ofNullable(getArtifactFromNode(repositoryId, n));
1836         } catch (RepositoryException e) {
1837             return Optional.empty();
1838         }
1839     }
1840
1841     private Optional<ArtifactMetadata> getArtifactOptional(final String repositoryId, final Row row) {
1842         try {
1843             return Optional.of(getArtifactFromNode(repositoryId, row.getNode("artifact")));
1844         } catch (RepositoryException e) {
1845             return Optional.empty();
1846         }
1847     }
1848
1849     @Override
1850     public Stream<ArtifactMetadata> getArtifactStream(final RepositorySession session, final String repositoryId,
1851                                                       final String namespace, final String projectId, final String projectVersion,
1852                                                       final QueryParameter queryParameter) throws MetadataResolutionException {
1853         final Session jcrSession;
1854         try {
1855             jcrSession = getSession(session);
1856         } catch (MetadataRepositoryException e) {
1857             throw new MetadataResolutionException(e.getMessage());
1858         }
1859
1860         try {
1861             Node root = jcrSession.getRootNode();
1862             String path = getProjectVersionPath(repositoryId, namespace, projectId, projectVersion);
1863
1864             if (root.hasNode(path)) {
1865                 Node node = root.getNode(path);
1866                 return StreamSupport.stream(JcrUtils.getChildNodes(node).spliterator(), false).filter(JcrMetadataRepository::isArtifactNodeType)
1867                         .map(n -> getArtifactOptional(repositoryId, n))
1868                         .map(Optional::get).skip(queryParameter.getOffset()).limit(queryParameter.getLimit());
1869             } else {
1870                 return Stream.empty();
1871             }
1872         } catch (RepositoryException e) {
1873             throw new MetadataResolutionException(e.getMessage(), e);
1874         }
1875     }
1876
1877     @Override
1878     public Stream<ArtifactMetadata> getArtifactStream(final RepositorySession session, final String repositoryId,
1879                                                       final QueryParameter queryParameter) throws MetadataResolutionException {
1880         final Session jcrSession;
1881         try {
1882             jcrSession = getSession(session);
1883         } catch (MetadataRepositoryException e) {
1884             throw new MetadataResolutionException(e.getMessage(), e);
1885         }
1886         List<ArtifactMetadata> artifacts;
1887
1888         String q = getArtifactQuery(repositoryId).toString();
1889
1890         try {
1891             Query query = jcrSession.getWorkspace().getQueryManager().createQuery(q, Query.JCR_SQL2);
1892             QueryResult result = query.execute();
1893
1894             return StreamSupport.stream(createResultSpliterator(result, getArtifactFromRowFunc(repositoryId)), false)
1895                     .filter(Optional::isPresent).map(Optional::get)
1896                     .skip(queryParameter.getOffset()).limit(queryParameter.getLimit());
1897
1898         } catch (RepositoryException | MetadataRepositoryException e) {
1899             throw new MetadataResolutionException(e.getMessage(), e);
1900         }
1901
1902     }
1903 }