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