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