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