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