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