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