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