]> source.dussan.org Git - archiva.git/blob
2b76db4aff83c1c6c932c6d9d47e5f319b02e67a
[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.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.google.common.collect.ImmutableMap;
47
48 import javax.jcr.InvalidItemStateException;
49 import javax.jcr.NamespaceRegistry;
50 import javax.jcr.Node;
51 import javax.jcr.NodeIterator;
52 import javax.jcr.PathNotFoundException;
53 import javax.jcr.Property;
54 import javax.jcr.Repository;
55 import javax.jcr.RepositoryException;
56 import javax.jcr.Session;
57 import javax.jcr.SimpleCredentials;
58 import javax.jcr.ValueFactory;
59 import javax.jcr.Workspace;
60 import javax.jcr.nodetype.NodeTypeManager;
61 import javax.jcr.nodetype.NodeTypeTemplate;
62 import javax.jcr.query.Query;
63 import javax.jcr.query.QueryManager;
64 import javax.jcr.query.QueryResult;
65 import javax.jcr.query.Row;
66 import javax.jcr.query.RowIterator;
67
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Calendar;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.Date;
74 import java.util.HashMap;
75 import java.util.Iterator;
76 import java.util.LinkedHashSet;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.Map.Entry;
80 import java.util.Set;
81
82 /**
83  * TODO below: revise storage format for project version metadata
84  * TODO revise reference storage
85  */
86 public class JcrMetadataRepository
87     implements MetadataRepository,RepositoryStatisticsProvider
88 {
89
90     private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
91
92     static final String NAMESPACE_NODE_TYPE = "archiva:namespace";
93
94     static final String PROJECT_NODE_TYPE = "archiva:project";
95
96     static final String PROJECT_VERSION_NODE_TYPE = "archiva:projectVersion";
97
98     static final String ARTIFACT_NODE_TYPE = "archiva:artifact";
99
100     static final String FACET_NODE_TYPE = "archiva:facet";
101
102     private static final String DEPENDENCY_NODE_TYPE = "archiva:dependency";
103
104     private final Map<String, MetadataFacetFactory> metadataFacetFactories;
105
106     private Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class );
107
108     private Repository repository;
109
110     private Session jcrSession;
111
112     public JcrMetadataRepository( Map<String, MetadataFacetFactory> metadataFacetFactories, Repository repository )
113         throws RepositoryException
114     {
115         this.metadataFacetFactories = metadataFacetFactories;
116         this.repository = repository;
117     }
118
119
120     public static void initialize( Session session )
121         throws RepositoryException
122     {
123
124         // TODO: consider using namespaces for facets instead of the current approach:
125         // (if used, check if actually called by normal injection)
126 //        for ( String facetId : metadataFacetFactories.keySet() )
127 //        {
128 //            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
129 //        }
130         Workspace workspace = session.getWorkspace();
131         NamespaceRegistry registry = workspace.getNamespaceRegistry();
132
133         if ( !Arrays.asList( registry.getPrefixes() ).contains( "archiva" ) )
134         {
135             registry.registerNamespace( "archiva", "http://archiva.apache.org/jcr/" );
136         }
137
138         NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
139         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.NAMESPACE_NODE_TYPE );
140         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_NODE_TYPE );
141         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_VERSION_NODE_TYPE );
142         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.ARTIFACT_NODE_TYPE );
143         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.FACET_NODE_TYPE );
144         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.DEPENDENCY_NODE_TYPE );
145
146     }
147
148     private static void registerMixinNodeType( NodeTypeManager nodeTypeManager, String name )
149         throws RepositoryException
150     {
151         NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
152         nodeType.setMixin( true );
153         nodeType.setName( name );
154
155         // for now just don't re-create - but in future if we change the definition, make sure to remove first as an
156         // upgrade path
157         if ( !nodeTypeManager.hasNodeType( name ) )
158         {
159             nodeTypeManager.registerNodeType( nodeType, false );
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         return artifacts;
763     }
764
765     @Override
766     public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId )
767         throws MetadataRepositoryException
768     {
769         String q =
770             "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
771                 + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) INNER JOIN [" + FACET_NODE_TYPE
772                 + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ([facet].[" + key + "] = $value)";
773
774         return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
775     }
776
777
778     @Override
779     public List<ArtifactMetadata> getArtifactsByMetadata( String key, String value, String repositoryId )
780         throws MetadataRepositoryException
781     {
782         String q =
783             "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact INNER JOIN [" + FACET_NODE_TYPE
784                 + "] AS facet ON ISCHILDNODE(facet, artifact) WHERE ([facet].[" + key + "] = $value)";
785
786         return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
787     }
788
789
790     @Override
791     public List<ArtifactMetadata> getArtifactsByProperty( String key, String value, String repositoryId )
792         throws MetadataRepositoryException
793     {
794         String q =
795             "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE
796                 + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE ([projectVersion].[" + key
797                 + "] = $value)";
798
799         return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) );
800     }
801
802
803     @Override
804     public void removeRepository( String repositoryId )
805         throws MetadataRepositoryException
806     {
807         try
808         {
809             Node root = getJcrSession().getRootNode();
810             String path = getRepositoryPath( repositoryId );
811             if ( root.hasNode( path ) )
812             {
813                 root.getNode( path ).remove();
814             }
815         }
816         catch ( RepositoryException e )
817         {
818             throw new MetadataRepositoryException( e.getMessage(), e );
819         }
820     }
821
822     @Override
823     public List<ArtifactMetadata> getArtifacts( String repositoryId )
824         throws MetadataRepositoryException
825     {
826         List<ArtifactMetadata> artifacts;
827
828         String q = getArtifactQuery( repositoryId );
829
830         try
831         {
832             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
833             QueryResult result = query.execute();
834
835             artifacts = new ArrayList<>();
836             for ( Node n : JcrUtils.getNodes( result ) )
837             {
838                 if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
839                 {
840                     artifacts.add( getArtifactFromNode( repositoryId, n ) );
841                 }
842             }
843         }
844         catch ( RepositoryException e )
845         {
846             throw new MetadataRepositoryException( e.getMessage(), e );
847         }
848         return artifacts;
849     }
850
851     private static String getArtifactQuery( String repositoryId )
852     {
853         return "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/" +
854             getRepositoryContentPath( repositoryId ) + "')";
855     }
856
857     @Override
858     public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
859         throws MetadataResolutionException
860     {
861         ProjectMetadata metadata = null;
862
863         try
864         {
865             Node root = getJcrSession().getRootNode();
866
867             // basically just checking it exists
868             String path = getProjectPath( repositoryId, namespace, projectId );
869             if ( root.hasNode( path ) )
870             {
871                 metadata = new ProjectMetadata();
872                 metadata.setId( projectId );
873                 metadata.setNamespace( namespace );
874             }
875         }
876         catch ( RepositoryException e )
877         {
878             throw new MetadataResolutionException( e.getMessage(), e );
879         }
880
881         return metadata;
882     }
883
884     @Override
885     public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
886                                                      String projectVersion )
887         throws MetadataResolutionException
888     {
889         ProjectVersionMetadata versionMetadata;
890
891         try
892         {
893             Node root = getJcrSession().getRootNode();
894
895             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
896             if ( !root.hasNode( path ) )
897             {
898                 return null;
899             }
900
901             Node node = root.getNode( path );
902
903             versionMetadata = new ProjectVersionMetadata();
904             versionMetadata.setId( projectVersion );
905             versionMetadata.setName( getPropertyString( node, "name" ) );
906             versionMetadata.setDescription( getPropertyString( node, "description" ) );
907             versionMetadata.setUrl( getPropertyString( node, "url" ) );
908             versionMetadata.setIncomplete(
909                 node.hasProperty( "incomplete" ) && node.getProperty( "incomplete" ).getBoolean() );
910
911             // FIXME: decide how to treat these in the content repo
912             String scmConnection = getPropertyString( node, "scm.connection" );
913             String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
914             String scmUrl = getPropertyString( node, "scm.url" );
915             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
916             {
917                 Scm scm = new Scm();
918                 scm.setConnection( scmConnection );
919                 scm.setDeveloperConnection( scmDeveloperConnection );
920                 scm.setUrl( scmUrl );
921                 versionMetadata.setScm( scm );
922             }
923
924             String ciSystem = getPropertyString( node, "ci.system" );
925             String ciUrl = getPropertyString( node, "ci.url" );
926             if ( ciSystem != null || ciUrl != null )
927             {
928                 CiManagement ci = new CiManagement();
929                 ci.setSystem( ciSystem );
930                 ci.setUrl( ciUrl );
931                 versionMetadata.setCiManagement( ci );
932             }
933
934             String issueSystem = getPropertyString( node, "issue.system" );
935             String issueUrl = getPropertyString( node, "issue.url" );
936             if ( issueSystem != null || issueUrl != null )
937             {
938                 IssueManagement issueManagement = new IssueManagement();
939                 issueManagement.setSystem( issueSystem );
940                 issueManagement.setUrl( issueUrl );
941                 versionMetadata.setIssueManagement( issueManagement );
942             }
943
944             String orgName = getPropertyString( node, "org.name" );
945             String orgUrl = getPropertyString( node, "org.url" );
946             if ( orgName != null || orgUrl != null )
947             {
948                 Organization org = new Organization();
949                 org.setName( orgName );
950                 org.setUrl( orgUrl );
951                 versionMetadata.setOrganization( org );
952             }
953
954             boolean done = false;
955             int i = 0;
956             while ( !done )
957             {
958                 String licenseName = getPropertyString( node, "license." + i + ".name" );
959                 String licenseUrl = getPropertyString( node, "license." + i + ".url" );
960                 if ( licenseName != null || licenseUrl != null )
961                 {
962                     License license = new License();
963                     license.setName( licenseName );
964                     license.setUrl( licenseUrl );
965                     versionMetadata.addLicense( license );
966                 }
967                 else
968                 {
969                     done = true;
970                 }
971                 i++;
972             }
973
974             done = false;
975             i = 0;
976             while ( !done )
977             {
978                 String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
979                 if ( mailingListName != null )
980                 {
981                     MailingList mailingList = new MailingList();
982                     mailingList.setName( mailingListName );
983                     mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
984                     String n = "mailingList." + i + ".otherArchives";
985                     if ( node.hasProperty( n ) )
986                     {
987                         mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
988                     }
989                     else
990                     {
991                         mailingList.setOtherArchives( Collections.<String>emptyList() );
992                     }
993                     mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
994                     mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
995                     mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
996                     versionMetadata.addMailingList( mailingList );
997                 }
998                 else
999                 {
1000                     done = true;
1001                 }
1002                 i++;
1003             }
1004
1005             if ( node.hasNode( "dependencies" ) )
1006             {
1007                 Node dependenciesNode = node.getNode( "dependencies" );
1008                 for ( Node n : JcrUtils.getChildNodes( dependenciesNode ) )
1009                 {
1010                     if ( n.isNodeType( DEPENDENCY_NODE_TYPE ) )
1011                     {
1012                         Dependency dependency = new Dependency();
1013                         // FIXME: correct these properties
1014                         dependency.setArtifactId( getPropertyString( n, "artifactId" ) );
1015                         dependency.setGroupId( getPropertyString( n, "groupId" ) );
1016                         dependency.setClassifier( getPropertyString( n, "classifier" ) );
1017                         dependency.setOptional( Boolean.valueOf( getPropertyString( n, "optional" ) ) );
1018                         dependency.setScope( getPropertyString( n, "scope" ) );
1019                         dependency.setSystemPath( getPropertyString( n, "systemPath" ) );
1020                         dependency.setType( getPropertyString( n, "type" ) );
1021                         dependency.setVersion( getPropertyString( n, "version" ) );
1022                         versionMetadata.addDependency( dependency );
1023                     }
1024                 }
1025             }
1026
1027             for ( Node n : JcrUtils.getChildNodes( node ) )
1028             {
1029                 if ( n.isNodeType( FACET_NODE_TYPE ) )
1030                 {
1031                     String name = n.getName();
1032                     MetadataFacetFactory factory = metadataFacetFactories.get( name );
1033                     if ( factory == null )
1034                     {
1035                         log.error( "Attempted to load unknown project version metadata facet: {}", name );
1036                     }
1037                     else
1038                     {
1039                         MetadataFacet facet = factory.createMetadataFacet();
1040                         Map<String, String> map = new HashMap<>();
1041                         for ( Property property : JcrUtils.getProperties( n ) )
1042                         {
1043                             String p = property.getName();
1044                             if ( !p.startsWith( "jcr:" ) )
1045                             {
1046                                 map.put( p, property.getString() );
1047                             }
1048                         }
1049                         facet.fromProperties( map );
1050                         versionMetadata.addFacet( facet );
1051                     }
1052                 }
1053             }
1054         }
1055         catch ( RepositoryException e )
1056         {
1057             throw new MetadataResolutionException( e.getMessage(), e );
1058         }
1059
1060         return versionMetadata;
1061     }
1062
1063     @Override
1064     public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
1065                                                    String projectVersion )
1066         throws MetadataResolutionException
1067     {
1068         Set<String> versions = new LinkedHashSet<String>();
1069
1070         try
1071         {
1072             Node root = getJcrSession().getRootNode();
1073
1074             Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) );
1075
1076             for ( Node n : JcrUtils.getChildNodes( node ) )
1077             {
1078                 versions.add( n.getProperty( "version" ).getString() );
1079             }
1080         }
1081         catch ( PathNotFoundException e )
1082         {
1083             // ignore repo not found for now
1084         }
1085         catch ( RepositoryException e )
1086         {
1087             throw new MetadataResolutionException( e.getMessage(), e );
1088         }
1089
1090         return versions;
1091     }
1092
1093     @Override
1094     public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
1095                                                                      String projectId, String projectVersion )
1096         throws MetadataResolutionException
1097     {
1098
1099         List<ProjectVersionReference> references = new ArrayList<>();
1100
1101         // TODO: bind variables instead
1102         String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId +
1103             "/content]) AND [groupId]='" + namespace + "' AND [artifactId]='" + projectId + "'";
1104         if ( projectVersion != null )
1105         {
1106             q += " AND [version]='" + projectVersion + "'";
1107         }
1108         try
1109         {
1110             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
1111             QueryResult result = query.execute();
1112
1113             for ( Node n : JcrUtils.getNodes( result ) )
1114             {
1115                 n = n.getParent(); // dependencies grouping element
1116
1117                 n = n.getParent(); // project version
1118                 String usedByProjectVersion = n.getName();
1119
1120                 n = n.getParent(); // project
1121                 String usedByProject = n.getName();
1122
1123                 n = n.getParent(); // namespace
1124                 String usedByNamespace = n.getProperty( "namespace" ).getString();
1125
1126                 ProjectVersionReference ref = new ProjectVersionReference();
1127                 ref.setNamespace( usedByNamespace );
1128                 ref.setProjectId( usedByProject );
1129                 ref.setProjectVersion( usedByProjectVersion );
1130                 ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY );
1131                 references.add( ref );
1132             }
1133         }
1134         catch ( RepositoryException e )
1135         {
1136             throw new MetadataResolutionException( e.getMessage(), e );
1137         }
1138
1139         return references;
1140     }
1141
1142     @Override
1143     public Collection<String> getRootNamespaces( String repositoryId )
1144         throws MetadataResolutionException
1145     {
1146         return getNamespaces( repositoryId, null );
1147     }
1148
1149     @Override
1150     public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
1151         throws MetadataResolutionException
1152     {
1153         String path = baseNamespace != null
1154             ? getNamespacePath( repositoryId, baseNamespace )
1155             : getRepositoryContentPath( repositoryId );
1156
1157         return getNodeNames( path, NAMESPACE_NODE_TYPE );
1158     }
1159
1160     @Override
1161     public Collection<String> getProjects( String repositoryId, String namespace )
1162         throws MetadataResolutionException
1163     {
1164         return getNodeNames( getNamespacePath( repositoryId, namespace ), PROJECT_NODE_TYPE );
1165     }
1166
1167     @Override
1168     public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
1169         throws MetadataResolutionException
1170     {
1171         return getNodeNames( getProjectPath( repositoryId, namespace, projectId ), PROJECT_VERSION_NODE_TYPE );
1172     }
1173
1174     @Override
1175     public void removeArtifact( ArtifactMetadata artifactMetadata, String baseVersion )
1176         throws MetadataRepositoryException
1177     {
1178
1179         String repositoryId = artifactMetadata.getRepositoryId();
1180
1181         try
1182         {
1183             Node root = getJcrSession().getRootNode();
1184             String path =
1185                 getProjectVersionPath( repositoryId, artifactMetadata.getNamespace(), artifactMetadata.getProject(),
1186                                        baseVersion );
1187
1188             if ( root.hasNode( path ) )
1189             {
1190                 Node node = root.getNode( path );
1191
1192                 for ( Node n : JcrUtils.getChildNodes( node ) )
1193                 {
1194                     if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1195                     {
1196                         if ( n.hasProperty( "version" ) )
1197                         {
1198                             String version = n.getProperty( "version" ).getString();
1199                             if ( StringUtils.equals( version, artifactMetadata.getVersion() ) )
1200                             {
1201                                 n.remove();
1202                             }
1203                         }
1204
1205                     }
1206                 }
1207             }
1208         }
1209         catch ( RepositoryException e )
1210         {
1211             throw new MetadataRepositoryException( e.getMessage(), e );
1212         }
1213
1214
1215     }
1216
1217
1218     @Override
1219     public void removeProjectVersion( String repoId, String namespace, String projectId, String projectVersion )
1220         throws MetadataRepositoryException
1221     {
1222         try
1223         {
1224
1225             String path = getProjectPath( repoId, namespace, projectId );
1226             Node root = getJcrSession().getRootNode();
1227
1228             Node nodeAtPath = root.getNode( path );
1229
1230             for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1231             {
1232                 if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) && StringUtils.equals( projectVersion,
1233                                                                                          node.getName() ) )
1234                 {
1235                     node.remove();
1236                 }
1237             }
1238         }
1239         catch ( RepositoryException e )
1240         {
1241             throw new MetadataRepositoryException( e.getMessage(), e );
1242         }
1243     }
1244
1245     @Override
1246     public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
1247                                 String id )
1248         throws MetadataRepositoryException
1249     {
1250         try
1251         {
1252             Node root = getJcrSession().getRootNode();
1253             String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id );
1254             if ( root.hasNode( path ) )
1255             {
1256                 root.getNode( path ).remove();
1257             }
1258
1259             // remove version
1260
1261             path = getProjectPath( repositoryId, namespace, projectId );
1262
1263             Node nodeAtPath = root.getNode( path );
1264
1265             for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1266             {
1267                 if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) //
1268                     && StringUtils.equals( node.getName(), projectVersion ) )
1269                 {
1270                     node.remove();
1271                 }
1272             }
1273         }
1274         catch ( RepositoryException e )
1275         {
1276             throw new MetadataRepositoryException( e.getMessage(), e );
1277         }
1278     }
1279
1280     @Override
1281     public void removeArtifact( String repositoryId, String namespace, String project, String projectVersion,
1282                                 MetadataFacet metadataFacet )
1283         throws MetadataRepositoryException
1284     {
1285         try
1286         {
1287             Node root = getJcrSession().getRootNode();
1288             String path = getProjectVersionPath( repositoryId, namespace, project, projectVersion );
1289
1290             if ( root.hasNode( path ) )
1291             {
1292                 Node node = root.getNode( path );
1293
1294                 for ( Node n : JcrUtils.getChildNodes( node ) )
1295                 {
1296                     if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1297                     {
1298                         ArtifactMetadata artifactMetadata = getArtifactFromNode( repositoryId, n );
1299                         log.debug( "artifactMetadata: {}", artifactMetadata );
1300                         MetadataFacet metadataFacetToRemove = artifactMetadata.getFacet( metadataFacet.getFacetId() );
1301                         if ( metadataFacetToRemove != null && metadataFacet.equals( metadataFacetToRemove ) )
1302                         {
1303                             n.remove();
1304                         }
1305                     }
1306                 }
1307             }
1308         }
1309         catch ( RepositoryException e )
1310         {
1311             throw new MetadataRepositoryException( e.getMessage(), e );
1312         }
1313     }
1314
1315     @Override
1316     public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
1317                                                       String projectVersion )
1318         throws MetadataResolutionException
1319     {
1320         List<ArtifactMetadata> artifacts = new ArrayList<>();
1321
1322         try
1323         {
1324             Node root = getJcrSession().getRootNode();
1325             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
1326
1327             if ( root.hasNode( path ) )
1328             {
1329                 Node node = root.getNode( path );
1330
1331                 for ( Node n : JcrUtils.getChildNodes( node ) )
1332                 {
1333                     if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1334                     {
1335                         artifacts.add( getArtifactFromNode( repositoryId, n ) );
1336                     }
1337                 }
1338             }
1339         }
1340         catch ( RepositoryException e )
1341         {
1342             throw new MetadataResolutionException( e.getMessage(), e );
1343         }
1344
1345         return artifacts;
1346     }
1347
1348     @Override
1349     public void save()
1350     {
1351         try
1352         {
1353             getJcrSession().save();
1354         } catch ( InvalidItemStateException e ) {
1355             // olamy this might happen when deleting a repo while is under scanning
1356             log.warn( "skip InvalidItemStateException:" + e.getMessage(), e );
1357         }
1358         catch ( RepositoryException e )
1359         {
1360             throw new RuntimeException( e.getMessage(), e );
1361         }
1362     }
1363
1364     @Override
1365     public void revert()
1366     {
1367         try
1368         {
1369             getJcrSession().refresh( false );
1370         }
1371         catch ( RepositoryException e )
1372         {
1373             throw new RuntimeException( e.getMessage(), e );
1374         }
1375     }
1376
1377     @Override
1378     public boolean canObtainAccess( Class<?> aClass )
1379     {
1380         return aClass == Session.class;
1381     }
1382
1383     @Override
1384     public <T>T obtainAccess( Class<T> aClass )
1385         throws MetadataRepositoryException
1386     {
1387         if ( aClass == Session.class )
1388         {
1389             try
1390             {
1391                 return (T) getJcrSession();
1392             }
1393             catch ( RepositoryException e )
1394             {
1395                 log.error( e.getMessage(), e );
1396                 throw new MetadataRepositoryException( e.getMessage(), e );
1397             }
1398         }
1399         throw new IllegalArgumentException(
1400             "Access using " + aClass + " is not supported on the JCR metadata storage" );
1401     }
1402
1403     @Override
1404     public void close()
1405         throws MetadataRepositoryException
1406     {
1407         if ( jcrSession != null && jcrSession.isLive() )
1408         {
1409             jcrSession.logout();
1410         }
1411     }
1412
1413
1414     /**
1415      * Exact is ignored as we can't do exact search in any property, we need a key
1416      */
1417     @Override
1418     public List<ArtifactMetadata> searchArtifacts( String text, String repositoryId, boolean exact )
1419         throws MetadataRepositoryException
1420     {
1421         return searchArtifacts( null, text, repositoryId, exact );
1422     }
1423
1424     @Override
1425     public List<ArtifactMetadata> searchArtifacts( String key, String text, String repositoryId, boolean exact )
1426         throws MetadataRepositoryException
1427     {
1428         // we can't do exact search in any property (*), we need a key
1429         boolean e = exact && key != null;
1430         String theKey = key == null ? "*" : "[" + key + "]";
1431         String projectVersionCondition =
1432             e ? "(projectVersion." + theKey + " = $value)" : "contains([projectVersion]." + theKey + ", $value)";
1433         String facetCondition = e ? "(facet." + theKey + " = $value)" : "contains([facet]." + theKey + ", $value)";
1434         String q =
1435             "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion LEFT OUTER JOIN ["
1436                 + ARTIFACT_NODE_TYPE + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) LEFT OUTER JOIN ["
1437                 + FACET_NODE_TYPE + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ("
1438                 + projectVersionCondition + " OR " + facetCondition + ")";
1439
1440         return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", text ) );
1441     }
1442
1443     private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1444         throws RepositoryException
1445     {
1446         String id = artifactNode.getName();
1447
1448         ArtifactMetadata artifact = new ArtifactMetadata();
1449         artifact.setId( id );
1450         artifact.setRepositoryId( repositoryId == null ? artifactNode.getAncestor(2).getName() : repositoryId );
1451
1452         Node projectVersionNode = artifactNode.getParent();
1453         Node projectNode = projectVersionNode.getParent();
1454         Node namespaceNode = projectNode.getParent();
1455
1456         artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1457         artifact.setProject( projectNode.getName() );
1458         artifact.setProjectVersion( projectVersionNode.getName() );
1459         artifact.setVersion( artifactNode.hasProperty( "version" )
1460                                  ? artifactNode.getProperty( "version" ).getString()
1461                                  : projectVersionNode.getName() );
1462
1463         if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) )
1464         {
1465             artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() );
1466         }
1467
1468         if ( artifactNode.hasProperty( "whenGathered" ) )
1469         {
1470             artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1471         }
1472
1473         if ( artifactNode.hasProperty( "size" ) )
1474         {
1475             artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1476         }
1477
1478         if ( artifactNode.hasProperty( "md5" ) )
1479         {
1480             artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1481         }
1482
1483         if ( artifactNode.hasProperty( "sha1" ) )
1484         {
1485             artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1486         }
1487
1488         for ( Node n : JcrUtils.getChildNodes( artifactNode ) )
1489         {
1490             if ( n.isNodeType( FACET_NODE_TYPE ) )
1491             {
1492                 String name = n.getName();
1493                 MetadataFacetFactory factory = metadataFacetFactories.get( name );
1494                 if ( factory == null )
1495                 {
1496                     log.error( "Attempted to load unknown project version metadata facet: " + name );
1497                 }
1498                 else
1499                 {
1500                     MetadataFacet facet = factory.createMetadataFacet();
1501                     Map<String, String> map = new HashMap<>();
1502                     for ( Property p : JcrUtils.getProperties( n ) )
1503                     {
1504                         String property = p.getName();
1505                         if ( !property.startsWith( "jcr:" ) )
1506                         {
1507                             map.put( property, p.getString() );
1508                         }
1509                     }
1510                     facet.fromProperties( map );
1511                     artifact.addFacet( facet );
1512                 }
1513             }
1514         }
1515         return artifact;
1516     }
1517
1518     private static String getPropertyString( Node node, String name )
1519         throws RepositoryException
1520     {
1521         return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
1522     }
1523
1524     private Collection<String> getNodeNames( String path, String nodeType )
1525         throws MetadataResolutionException
1526     {
1527         List<String> names = new ArrayList<>();
1528
1529         try
1530         {
1531             Node root = getJcrSession().getRootNode();
1532
1533             Node nodeAtPath = root.getNode( path );
1534
1535             for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1536             {
1537                 if ( node.isNodeType( nodeType ) )
1538                 {
1539                     names.add( node.getName() );
1540                 }
1541             }
1542         }
1543         catch ( PathNotFoundException e )
1544         {
1545             // ignore repo not found for now
1546         }
1547         catch ( RepositoryException e )
1548         {
1549             throw new MetadataResolutionException( e.getMessage(), e );
1550         }
1551
1552         return names;
1553     }
1554
1555     private static String getRepositoryPath( String repositoryId )
1556     {
1557         return "repositories/" + repositoryId;
1558     }
1559
1560     private static String getRepositoryContentPath( String repositoryId )
1561     {
1562         return getRepositoryPath( repositoryId ) + "/content/";
1563     }
1564
1565     private static String getFacetPath( String repositoryId, String facetId )
1566     {
1567         return getRepositoryPath( repositoryId ) + "/facets/" + facetId;
1568     }
1569
1570     private static String getNamespacePath( String repositoryId, String namespace )
1571     {
1572         return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' );
1573     }
1574
1575     private static String getProjectPath( String repositoryId, String namespace, String projectId )
1576     {
1577         return getNamespacePath( repositoryId, namespace ) + "/" + projectId;
1578     }
1579
1580     private static String getProjectVersionPath( String repositoryId, String namespace, String projectId,
1581                                                  String projectVersion )
1582     {
1583         return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion;
1584     }
1585
1586     private static String getArtifactPath( String repositoryId, String namespace, String projectId,
1587                                            String projectVersion, String id )
1588     {
1589         return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id;
1590     }
1591
1592     private Node getOrAddNodeByPath( Node baseNode, String name )
1593         throws RepositoryException
1594     {
1595         return getOrAddNodeByPath( baseNode, name, null );
1596     }
1597
1598     private Node getOrAddNodeByPath( Node baseNode, String name, String nodeType )
1599         throws RepositoryException
1600     {
1601         Node node = baseNode;
1602         for ( String n : name.split( "/" ) )
1603         {
1604             node = JcrUtils.getOrAddNode( node, n );
1605             if ( nodeType != null )
1606             {
1607                 node.addMixin( nodeType );
1608             }
1609         }
1610         return node;
1611     }
1612
1613     private static String getFacetPath( String repositoryId, String facetId, String name )
1614     {
1615         return getFacetPath( repositoryId, facetId ) + "/" + name;
1616     }
1617
1618     private Node getOrAddRepositoryNode( String repositoryId )
1619         throws RepositoryException
1620     {
1621         Node root = getJcrSession().getRootNode();
1622         Node node = JcrUtils.getOrAddNode( root, "repositories" );
1623         node = JcrUtils.getOrAddNode( node, repositoryId );
1624         return node;
1625     }
1626
1627     private Node getOrAddRepositoryContentNode( String repositoryId )
1628         throws RepositoryException
1629     {
1630         Node node = getOrAddRepositoryNode( repositoryId );
1631         return JcrUtils.getOrAddNode( node, "content" );
1632     }
1633
1634     private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1635         throws RepositoryException
1636     {
1637         Node repo = getOrAddRepositoryContentNode( repositoryId );
1638         return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ), NAMESPACE_NODE_TYPE );
1639     }
1640
1641     private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1642         throws RepositoryException
1643     {
1644         Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1645         Node node = JcrUtils.getOrAddNode( namespaceNode, projectId );
1646         node.addMixin( PROJECT_NODE_TYPE );
1647         return node;
1648     }
1649
1650     private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1651                                              String projectVersion )
1652         throws RepositoryException
1653     {
1654         Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1655         Node node = JcrUtils.getOrAddNode( projectNode, projectVersion );
1656         node.addMixin( PROJECT_VERSION_NODE_TYPE );
1657         return node;
1658     }
1659
1660     private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1661                                        String id )
1662         throws RepositoryException
1663     {
1664         Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1665         Node node = JcrUtils.getOrAddNode( versionNode, id );
1666         node.addMixin( ARTIFACT_NODE_TYPE );
1667         return node;
1668     }
1669
1670     private static Calendar createCalendar( Date time )
1671     {
1672         Calendar cal = Calendar.getInstance();
1673         cal.setTime( time );
1674         return cal;
1675     }
1676
1677     private String join( Collection<String> ids )
1678     {
1679         if ( ids != null && !ids.isEmpty() )
1680         {
1681             StringBuilder s = new StringBuilder();
1682             for ( String id : ids )
1683             {
1684                 s.append( id );
1685                 s.append( "," );
1686             }
1687             return s.substring( 0, s.length() - 1 );
1688         }
1689         return null;
1690     }
1691
1692     public Session getJcrSession()
1693         throws RepositoryException
1694     {
1695         if ( this.jcrSession == null || !this.jcrSession.isLive() )
1696         {
1697             jcrSession = repository.login( new SimpleCredentials( "admin", "admin".toCharArray() ) );
1698         }
1699         return this.jcrSession;
1700     }
1701
1702     @Override
1703     public void populateStatistics( MetadataRepository repository, String repositoryId,
1704                                             RepositoryStatistics repositoryStatistics )
1705         throws MetadataRepositoryException
1706     {
1707         if (!(repository instanceof JcrMetadataRepository)) {
1708             throw new MetadataRepositoryException( "The statistics population is only possible for JcrMetdataRepository implementations" );
1709         }
1710         Session session = (Session) repository.obtainAccess( Session.class );
1711         // TODO: these may be best as running totals, maintained by observations on the properties in JCR
1712
1713         try
1714         {
1715             QueryManager queryManager = session.getWorkspace().getQueryManager();
1716
1717             // TODO: JCR-SQL2 query will not complete on a large repo in Jackrabbit 2.2.0 - see JCR-2835
1718             //    Using the JCR-SQL2 variants gives
1719             //      "org.apache.lucene.search.BooleanQuery$TooManyClauses: maxClauseCount is set to 1024"
1720 //            String whereClause = "WHERE ISDESCENDANTNODE([/repositories/" + repositoryId + "/content])";
1721 //            Query query = queryManager.createQuery( "SELECT size FROM [archiva:artifact] " + whereClause,
1722 //                                                    Query.JCR_SQL2 );
1723             String whereClause = "WHERE jcr:path LIKE '/repositories/" + repositoryId + "/content/%'";
1724             Query query = queryManager.createQuery( "SELECT size FROM archiva:artifact " + whereClause, Query.SQL );
1725
1726             QueryResult queryResult = query.execute();
1727
1728             Map<String, Integer> totalByType = new HashMap<>();
1729             long totalSize = 0, totalArtifacts = 0;
1730             for ( Row row : JcrUtils.getRows( queryResult ) )
1731             {
1732                 Node n = row.getNode();
1733                 totalSize += row.getValue( "size" ).getLong();
1734
1735                 String type;
1736                 if ( n.hasNode( MavenArtifactFacet.FACET_ID ) )
1737                 {
1738                     Node facetNode = n.getNode( MavenArtifactFacet.FACET_ID );
1739                     type = facetNode.getProperty( "type" ).getString();
1740                 }
1741                 else
1742                 {
1743                     type = "Other";
1744                 }
1745                 Integer prev = totalByType.get( type );
1746                 totalByType.put( type, prev != null ? prev + 1 : 1 );
1747
1748                 totalArtifacts++;
1749             }
1750
1751             repositoryStatistics.setTotalArtifactCount( totalArtifacts );
1752             repositoryStatistics.setTotalArtifactFileSize( totalSize );
1753             for ( Map.Entry<String, Integer> entry : totalByType.entrySet() )
1754             {
1755                 System.out.println("Setting count for type: "+entry.getKey()+" = "+entry.getValue());
1756                 repositoryStatistics.setTotalCountForType( entry.getKey(), entry.getValue() );
1757             }
1758
1759             // The query ordering is a trick to ensure that the size is correct, otherwise due to lazy init it will be -1
1760 //            query = queryManager.createQuery( "SELECT * FROM [archiva:project] " + whereClause, Query.JCR_SQL2 );
1761             query = queryManager.createQuery( "SELECT * FROM archiva:project " + whereClause + " ORDER BY jcr:score",
1762                 Query.SQL );
1763             repositoryStatistics.setTotalProjectCount( query.execute().getRows().getSize() );
1764
1765 //            query = queryManager.createQuery(
1766 //                "SELECT * FROM [archiva:namespace] " + whereClause + " AND namespace IS NOT NULL", Query.JCR_SQL2 );
1767             query = queryManager.createQuery(
1768                 "SELECT * FROM archiva:namespace " + whereClause + " AND namespace IS NOT NULL ORDER BY jcr:score",
1769                 Query.SQL );
1770             repositoryStatistics.setTotalGroupCount( query.execute().getRows().getSize() );
1771         }
1772         catch ( RepositoryException e )
1773         {
1774             throw new MetadataRepositoryException( e.getMessage(), e );
1775         }
1776     }
1777
1778 }