This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.metadata.repository.jcr;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import com.google.common.collect.ImmutableMap;
023import org.apache.archiva.checksum.ChecksumAlgorithm;
024import org.apache.archiva.metadata.QueryParameter;
025import org.apache.archiva.metadata.model.*;
026import org.apache.archiva.metadata.model.maven2.MavenArtifactFacet;
027import org.apache.archiva.metadata.repository.*;
028import org.apache.archiva.metadata.repository.stats.model.RepositoryStatistics;
029import org.apache.archiva.metadata.repository.stats.model.RepositoryStatisticsProvider;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.jackrabbit.JcrConstants;
032import org.apache.jackrabbit.commons.JcrUtils;
033import org.apache.jackrabbit.commons.cnd.CndImporter;
034import org.apache.jackrabbit.commons.cnd.ParseException;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import javax.annotation.ParametersAreNonnullByDefault;
039import javax.jcr.*;
040import javax.jcr.query.*;
041import java.io.IOException;
042import java.io.InputStreamReader;
043import java.io.Reader;
044import java.time.ZonedDateTime;
045import java.util.*;
046import java.util.Map.Entry;
047import java.util.function.Consumer;
048import java.util.function.Function;
049import java.util.stream.Collectors;
050import java.util.stream.Stream;
051import java.util.stream.StreamSupport;
052
053import static javax.jcr.Property.JCR_LAST_MODIFIED;
054import static org.apache.archiva.metadata.repository.jcr.JcrConstants.*;
055
056/**
057 * TODO below: revise storage format for project version metadata
058 * TODO revise reference storage
059 */
060@ParametersAreNonnullByDefault
061public class JcrMetadataRepository
062        extends AbstractMetadataRepository implements MetadataRepository, RepositoryStatisticsProvider {
063
064
065    private static final String QUERY_ARTIFACT_1 = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/";
066
067    static final String QUERY_ARTIFACTS_BY_PROJECT_VERSION_1 = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
068            + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) INNER JOIN [" + FACET_NODE_TYPE
069            + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ([facet].[";
070    static final String QUERY_ARTIFACTS_BY_PROJECT_VERSION_2 = "] = $value)";
071
072    static final String QUERY_ARTIFACTS_BY_METADATA_1 = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact INNER JOIN [" + FACET_NODE_TYPE
073            + "] AS facet ON ISCHILDNODE(facet, artifact) WHERE ([facet].[";
074    static final String QUERY_ARTIFACTS_BY_METADATA_2 = "] = $value)";
075
076    static final String QUERY_ARTIFACTS_BY_PROPERTY_1 = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
077            + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE ([projectVersion].[";
078    static final String QUERY_ARTIFACTS_BY_PROPERTY_2 = "] = $value)";
079
080
081    private static final String QUERY_ARTIFACT_2 = "')";
082
083    private Logger log = LoggerFactory.getLogger(JcrMetadataRepository.class);
084
085    private Repository repository;
086
087    public JcrMetadataRepository(MetadataService metadataService, Repository repository)
088            throws RepositoryException {
089        super(metadataService);
090        this.repository = repository;
091    }
092
093
094    public static void initializeNodeTypes(Session session)
095            throws RepositoryException {
096
097        // TODO: consider using namespaces for facets instead of the current approach:
098        // (if used, check if actually called by normal injection)
099//        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            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        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                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                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                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                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                        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                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        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}