]> source.dussan.org Git - archiva.git/blob
3e2b6c11a5ab767668ee0b22af0b528243f1bc9b
[archiva.git] /
1 package org.apache.archiva.metadata.repository.jcr;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.metadata.model.ArtifactMetadata;
23 import org.apache.archiva.metadata.model.CiManagement;
24 import org.apache.archiva.metadata.model.Dependency;
25 import org.apache.archiva.metadata.model.IssueManagement;
26 import org.apache.archiva.metadata.model.License;
27 import org.apache.archiva.metadata.model.MailingList;
28 import org.apache.archiva.metadata.model.MetadataFacet;
29 import org.apache.archiva.metadata.model.MetadataFacetFactory;
30 import org.apache.archiva.metadata.model.Organization;
31 import org.apache.archiva.metadata.model.ProjectMetadata;
32 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
33 import org.apache.archiva.metadata.model.ProjectVersionReference;
34 import org.apache.archiva.metadata.model.Scm;
35 import org.apache.archiva.metadata.repository.MetadataRepository;
36 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
37 import org.apache.archiva.metadata.repository.MetadataResolutionException;
38 import org.apache.jackrabbit.commons.JcrUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Calendar;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.Date;
48 import java.util.HashMap;
49 import java.util.LinkedHashSet;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 import javax.jcr.NamespaceRegistry;
54 import javax.jcr.Node;
55 import javax.jcr.NodeIterator;
56 import javax.jcr.PathNotFoundException;
57 import javax.jcr.Property;
58 import javax.jcr.Repository;
59 import javax.jcr.RepositoryException;
60 import javax.jcr.Session;
61 import javax.jcr.SimpleCredentials;
62 import javax.jcr.ValueFactory;
63 import javax.jcr.Workspace;
64 import javax.jcr.nodetype.NodeTypeManager;
65 import javax.jcr.nodetype.NodeTypeTemplate;
66 import javax.jcr.query.Query;
67 import javax.jcr.query.QueryResult;
68
69 /**
70  * @todo below: revise storage format for project version metadata
71  * @todo revise reference storage
72  */
73 public class JcrMetadataRepository
74     implements MetadataRepository
75 {
76     private static final String JCR_LAST_MODIFIED = "jcr:lastModified";
77
78     static final String NAMESPACE_NODE_TYPE = "archiva:namespace";
79
80     static final String PROJECT_NODE_TYPE = "archiva:project";
81
82     static final String PROJECT_VERSION_NODE_TYPE = "archiva:projectVersion";
83
84     static final String ARTIFACT_NODE_TYPE = "archiva:artifact";
85
86     static final String FACET_NODE_TYPE = "archiva:facet";
87
88     private static final String DEPENDENCY_NODE_TYPE = "archiva:dependency";
89
90     private final Map<String, MetadataFacetFactory> metadataFacetFactories;
91
92     private static final Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class );
93
94     private Repository repository;
95
96     private Session session;
97
98     public JcrMetadataRepository( Map<String, MetadataFacetFactory> metadataFacetFactories, Repository repository )
99         throws RepositoryException
100     {
101         this.metadataFacetFactories = metadataFacetFactories;
102         this.repository = repository;
103
104         session = repository.login( new SimpleCredentials( "admin", "admin".toCharArray() ) );
105     }
106
107     static void initialize( Session session )
108         throws RepositoryException
109     {
110         // TODO: consider using namespaces for facets instead of the current approach:
111         // (if used, check if actually called by normal injection)
112 //        for ( String facetId : metadataFacetFactories.keySet() )
113 //        {
114 //            session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId );
115 //        }
116
117         Workspace workspace = session.getWorkspace();
118         NamespaceRegistry registry = workspace.getNamespaceRegistry();
119
120         if ( !Arrays.asList( registry.getPrefixes() ).contains( "archiva" ) )
121         {
122             registry.registerNamespace( "archiva", "http://archiva.apache.org/jcr/" );
123         }
124
125         NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager();
126         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.NAMESPACE_NODE_TYPE );
127         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_NODE_TYPE );
128         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_VERSION_NODE_TYPE );
129         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.ARTIFACT_NODE_TYPE );
130         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.FACET_NODE_TYPE );
131         registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.DEPENDENCY_NODE_TYPE );
132     }
133
134     private static void registerMixinNodeType( NodeTypeManager nodeTypeManager, String name )
135         throws RepositoryException
136     {
137         NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate();
138         nodeType.setMixin( true );
139         nodeType.setName( name );
140
141         // for now just don't re-create - but in future if we change the definition, make sure to remove first as an
142         // upgrade path
143         if ( !nodeTypeManager.hasNodeType( name ) )
144         {
145             nodeTypeManager.registerNodeType( nodeType, false );
146         }
147     }
148
149     public void updateProject( String repositoryId, ProjectMetadata project )
150         throws MetadataRepositoryException
151     {
152         updateProject( repositoryId, project.getNamespace(), project.getId() );
153     }
154
155     private void updateProject( String repositoryId, String namespace, String projectId )
156         throws MetadataRepositoryException
157     {
158         updateNamespace( repositoryId, namespace );
159
160         try
161         {
162             getOrAddProjectNode( repositoryId, namespace, projectId );
163         }
164         catch ( RepositoryException e )
165         {
166             throw new MetadataRepositoryException( e.getMessage(), e );
167         }
168     }
169
170     public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
171                                 ArtifactMetadata artifactMeta )
172         throws MetadataRepositoryException
173     {
174         updateNamespace( repositoryId, namespace );
175
176         try
177         {
178             Node node = getOrAddArtifactNode( repositoryId, namespace, projectId, projectVersion,
179                                               artifactMeta.getId() );
180
181             Calendar cal = Calendar.getInstance();
182             cal.setTime( artifactMeta.getFileLastModified() );
183             node.setProperty( JCR_LAST_MODIFIED, cal );
184
185             cal = Calendar.getInstance();
186             cal.setTime( artifactMeta.getWhenGathered() );
187             node.setProperty( "whenGathered", cal );
188
189             node.setProperty( "size", artifactMeta.getSize() );
190             node.setProperty( "md5", artifactMeta.getMd5() );
191             node.setProperty( "sha1", artifactMeta.getSha1() );
192
193             node.setProperty( "version", artifactMeta.getVersion() );
194
195             for ( MetadataFacet facet : artifactMeta.getFacetList() )
196             {
197                 if ( node.hasNode( facet.getFacetId() ) )
198                 {
199                     node.getNode( facet.getFacetId() ).remove();
200                 }
201
202                 // recreate, to ensure properties are removed
203                 Node n = node.addNode( facet.getFacetId() );
204                 n.addMixin( FACET_NODE_TYPE );
205
206                 for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() )
207                 {
208                     n.setProperty( entry.getKey(), entry.getValue() );
209                 }
210             }
211         }
212         catch ( RepositoryException e )
213         {
214             throw new MetadataRepositoryException( e.getMessage(), e );
215         }
216     }
217
218     public void updateProjectVersion( String repositoryId, String namespace, String projectId,
219                                       ProjectVersionMetadata versionMetadata )
220         throws MetadataRepositoryException
221     {
222         updateProject( repositoryId, namespace, projectId );
223
224         try
225         {
226             Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId,
227                                                            versionMetadata.getId() );
228
229             versionNode.setProperty( "name", versionMetadata.getName() );
230             versionNode.setProperty( "description", versionMetadata.getDescription() );
231             versionNode.setProperty( "url", versionMetadata.getUrl() );
232             versionNode.setProperty( "incomplete", versionMetadata.isIncomplete() );
233
234             // FIXME: decide how to treat these in the content repo
235             if ( versionMetadata.getScm() != null )
236             {
237                 versionNode.setProperty( "scm.connection", versionMetadata.getScm().getConnection() );
238                 versionNode.setProperty( "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() );
239                 versionNode.setProperty( "scm.url", versionMetadata.getScm().getUrl() );
240             }
241             if ( versionMetadata.getCiManagement() != null )
242             {
243                 versionNode.setProperty( "ci.system", versionMetadata.getCiManagement().getSystem() );
244                 versionNode.setProperty( "ci.url", versionMetadata.getCiManagement().getUrl() );
245             }
246             if ( versionMetadata.getIssueManagement() != null )
247             {
248                 versionNode.setProperty( "issue.system", versionMetadata.getIssueManagement().getSystem() );
249                 versionNode.setProperty( "issue.url", versionMetadata.getIssueManagement().getUrl() );
250             }
251             if ( versionMetadata.getOrganization() != null )
252             {
253                 versionNode.setProperty( "org.name", versionMetadata.getOrganization().getName() );
254                 versionNode.setProperty( "org.url", versionMetadata.getOrganization().getUrl() );
255             }
256             int i = 0;
257             for ( License license : versionMetadata.getLicenses() )
258             {
259                 versionNode.setProperty( "license." + i + ".name", license.getName() );
260                 versionNode.setProperty( "license." + i + ".url", license.getUrl() );
261                 i++;
262             }
263             i = 0;
264             for ( MailingList mailingList : versionMetadata.getMailingLists() )
265             {
266                 versionNode.setProperty( "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() );
267                 versionNode.setProperty( "mailingList." + i + ".name", mailingList.getName() );
268                 versionNode.setProperty( "mailingList." + i + ".post", mailingList.getPostAddress() );
269                 versionNode.setProperty( "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() );
270                 versionNode.setProperty( "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() );
271                 versionNode.setProperty( "mailingList." + i + ".otherArchives", join(
272                     mailingList.getOtherArchives() ) );
273                 i++;
274             }
275
276             if ( !versionMetadata.getDependencies().isEmpty() )
277             {
278                 Node dependenciesNode = JcrUtils.getOrAddNode( versionNode, "dependencies" );
279
280                 for ( Dependency dependency : versionMetadata.getDependencies() )
281                 {
282                     // Note that we deliberately don't alter the namespace path - not enough dependencies for
283                     // number of nodes at a given depth to be an issue. Similarly, we don't add subnodes for each
284                     // component of the ID as that creates extra depth and causes a great cost in space and memory
285
286                     // FIXME: change group ID to namespace
287                     // FIXME: change to artifact's ID - this is constructed by the Maven 2 format for now.
288                     //        This won't support types where the extension doesn't match the type.
289                     //        (see also Maven2RepositoryStorage#readProjectVersionMetadata construction of POM)
290                     String id =
291                         dependency.getGroupId() + ";" + dependency.getArtifactId() + "-" + dependency.getVersion();
292                     if ( dependency.getClassifier() != null )
293                     {
294                         id += "-" + dependency.getClassifier();
295                     }
296                     id += "." + dependency.getType();
297
298                     Node n = JcrUtils.getOrAddNode( dependenciesNode, id );
299                     n.addMixin( DEPENDENCY_NODE_TYPE );
300
301                     // FIXME: remove temp code just to make it keep working
302                     n.setProperty( "groupId", dependency.getGroupId() );
303                     n.setProperty( "artifactId", dependency.getArtifactId() );
304                     n.setProperty( "version", dependency.getVersion() );
305                     n.setProperty( "type", dependency.getType() );
306                     n.setProperty( "classifier", dependency.getClassifier() );
307                     n.setProperty( "scope", dependency.getScope() );
308                     n.setProperty( "systemPath", dependency.getSystemPath() );
309                     n.setProperty( "optional", dependency.isOptional() );
310
311                     // node has no native content at this time, just facets
312                     // no need to list a type as it's implied by the path. Parents are Maven specific.
313
314                     // FIXME: add scope, systemPath, type, version, classifier & maven2 specific IDs as a facet
315                     //        (should also have been added to the Dependency)
316
317                     // TODO: add a property that is a weak reference to the originating artifact, creating it if
318                     //       necessary (without adding the archiva:artifact mixin so that it doesn't get listed as an
319                     //       artifact, which gives a different meaning to "incomplete" which is a known local project
320                     //       that doesn't have metadata yet but has artifacts). (Though we may want to give it the
321                     //       artifact mixin and another property to identify all non-local artifacts for the closure
322                     //       reports)
323                 }
324             }
325
326             for ( MetadataFacet facet : versionMetadata.getFacetList() )
327             {
328                 // recreate, to ensure properties are removed
329                 if ( versionNode.hasNode( facet.getFacetId() ) )
330                 {
331                     versionNode.getNode( facet.getFacetId() ).remove();
332                 }
333                 Node n = versionNode.addNode( facet.getFacetId() );
334                 n.addMixin( FACET_NODE_TYPE );
335
336                 for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() )
337                 {
338                     n.setProperty( entry.getKey(), entry.getValue() );
339                 }
340             }
341         }
342         catch ( RepositoryException e )
343         {
344             throw new MetadataRepositoryException( e.getMessage(), e );
345         }
346     }
347
348     // FIXME: remove this and projectversionreference
349     public void updateProjectReference( String repositoryId, String namespace, String projectId, String projectVersion,
350                                         ProjectVersionReference reference )
351         throws MetadataRepositoryException
352     {
353         // not using weak references, since they still need to exist upfront to be referred to
354         try
355         {
356             Node node = getOrAddRepositoryContentNode( repositoryId );
357             node = JcrUtils.getOrAddNode( node, namespace );
358             node = JcrUtils.getOrAddNode( node, projectId );
359             node = JcrUtils.getOrAddNode( node, projectVersion );
360             node = JcrUtils.getOrAddNode( node, "references" );
361             node = JcrUtils.getOrAddNode( node, reference.getNamespace() );
362             node = JcrUtils.getOrAddNode( node, reference.getProjectId() );
363             node = JcrUtils.getOrAddNode( node, reference.getProjectVersion() );
364             node.setProperty( "type", reference.getReferenceType().toString() );
365         }
366         catch ( RepositoryException e )
367         {
368             throw new MetadataRepositoryException( e.getMessage(), e );
369         }
370     }
371
372     public void updateNamespace( String repositoryId, String namespace )
373         throws MetadataRepositoryException
374     {
375         try
376         {
377             Node node = getOrAddNamespaceNode( repositoryId, namespace );
378             node.setProperty( "namespace", namespace );
379         }
380         catch ( RepositoryException e )
381         {
382             throw new MetadataRepositoryException( e.getMessage(), e );
383         }
384     }
385
386     public List<String> getMetadataFacets( String repositoryId, String facetId )
387         throws MetadataRepositoryException
388     {
389         List<String> facets = new ArrayList<String>();
390
391         try
392         {
393             // no need to construct node-by-node here, as we'll find in the next instance, the facet names have / and
394             // are paths themselves
395             Node node = session.getRootNode().getNode( getFacetPath( repositoryId, facetId ) );
396
397             // TODO: this is a bit awkward. Might be better to review the purpose of this function - why is the list of
398             //   paths helpful?
399             recurse( facets, "", node );
400         }
401         catch ( PathNotFoundException e )
402         {
403             // ignored - the facet doesn't exist, so return the empty list
404         }
405         catch ( RepositoryException e )
406         {
407             throw new MetadataRepositoryException( e.getMessage(), e );
408         }
409         return facets;
410     }
411
412     private void recurse( List<String> facets, String prefix, Node node )
413         throws RepositoryException
414     {
415         for ( Node n : JcrUtils.getChildNodes( node ) )
416         {
417             String name = prefix + "/" + n.getName();
418             if ( n.hasNodes() )
419             {
420                 recurse( facets, name, n );
421             }
422             else
423             {
424                 // strip leading / first
425                 facets.add( name.substring( 1 ) );
426             }
427         }
428     }
429
430     public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name )
431         throws MetadataRepositoryException
432     {
433         MetadataFacet metadataFacet = null;
434         try
435         {
436             Node root = session.getRootNode();
437             Node node = root.getNode( getFacetPath( repositoryId, facetId, name ) );
438
439             MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId );
440             if ( metadataFacetFactory != null )
441             {
442                 metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name );
443                 Map<String, String> map = new HashMap<String, String>();
444                 for ( Property property : JcrUtils.getProperties( node ) )
445                 {
446                     String p = property.getName();
447                     if ( !p.startsWith( "jcr:" ) )
448                     {
449                         map.put( p, property.getString() );
450                     }
451                 }
452                 metadataFacet.fromProperties( map );
453             }
454         }
455         catch ( PathNotFoundException e )
456         {
457             // ignored - the facet doesn't exist, so return null
458         }
459         catch ( RepositoryException e )
460         {
461             throw new MetadataRepositoryException( e.getMessage(), e );
462         }
463         return metadataFacet;
464     }
465
466     public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
467         throws MetadataRepositoryException
468     {
469         try
470         {
471             Node repo = getOrAddRepositoryNode( repositoryId );
472             Node facets = JcrUtils.getOrAddNode( repo, "facets" );
473
474             String id = metadataFacet.getFacetId();
475             Node facetNode = JcrUtils.getOrAddNode( facets, id );
476
477             Node node = getOrAddNodeByPath( facetNode, metadataFacet.getName() );
478
479             for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() )
480             {
481                 node.setProperty( entry.getKey(), entry.getValue() );
482             }
483         }
484         catch ( RepositoryException e )
485         {
486             throw new MetadataRepositoryException( e.getMessage(), e );
487         }
488     }
489
490     public void removeMetadataFacets( String repositoryId, String facetId )
491         throws MetadataRepositoryException
492     {
493         try
494         {
495             Node root = session.getRootNode();
496             String path = getFacetPath( repositoryId, facetId );
497             if ( root.hasNode( path ) )
498             {
499                 root.getNode( path ).remove();
500             }
501         }
502         catch ( RepositoryException e )
503         {
504             throw new MetadataRepositoryException( e.getMessage(), e );
505         }
506     }
507
508     public void removeMetadataFacet( String repositoryId, String facetId, String name )
509         throws MetadataRepositoryException
510     {
511         try
512         {
513             Node root = session.getRootNode();
514             String path = getFacetPath( repositoryId, facetId, name );
515             if ( root.hasNode( path ) )
516             {
517                 Node node = root.getNode( path );
518                 do
519                 {
520                     // also remove empty container nodes
521                     Node parent = node.getParent();
522                     node.remove();
523                     node = parent;
524                 }
525                 while ( !node.hasNodes() );
526             }
527         }
528         catch ( RepositoryException e )
529         {
530             throw new MetadataRepositoryException( e.getMessage(), e );
531         }
532     }
533
534     public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
535         throws MetadataRepositoryException
536     {
537         List<ArtifactMetadata> artifacts;
538
539         String q = getArtifactQuery( repoId );
540
541         if ( startTime != null )
542         {
543             q += " AND [whenGathered] >= $start";
544         }
545         if ( endTime != null )
546         {
547             q += " AND [whenGathered] <= $end";
548         }
549
550         try
551         {
552             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
553             ValueFactory valueFactory = session.getValueFactory();
554             if ( startTime != null )
555             {
556                 query.bindValue( "start", valueFactory.createValue( createCalendar( startTime ) ) );
557             }
558             if ( endTime != null )
559             {
560                 query.bindValue( "end", valueFactory.createValue( createCalendar( endTime ) ) );
561             }
562             QueryResult result = query.execute();
563
564             artifacts = new ArrayList<ArtifactMetadata>();
565             for ( Node n : JcrUtils.getNodes( result ) )
566             {
567                 artifacts.add( getArtifactFromNode( repoId, n ) );
568             }
569         }
570         catch ( RepositoryException e )
571         {
572             throw new MetadataRepositoryException( e.getMessage(), e );
573         }
574         return artifacts;
575     }
576
577     public Collection<String> getRepositories()
578         throws MetadataRepositoryException
579     {
580         List<String> repositories;
581
582         try
583         {
584             Node root = session.getRootNode();
585             if ( root.hasNode( "repositories" ) )
586             {
587                 Node node = root.getNode( "repositories" );
588
589                 repositories = new ArrayList<String>();
590                 NodeIterator i = node.getNodes();
591                 while ( i.hasNext() )
592                 {
593                     Node n = i.nextNode();
594                     repositories.add( n.getName() );
595                 }
596             }
597             else
598             {
599                 repositories = Collections.emptyList();
600             }
601         }
602         catch ( RepositoryException e )
603         {
604             throw new MetadataRepositoryException( e.getMessage(), e );
605         }
606         return repositories;
607     }
608
609     public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
610         throws MetadataRepositoryException
611     {
612         List<ArtifactMetadata> artifacts;
613
614         String q = getArtifactQuery( repositoryId ) + " AND ([sha1] = $checksum OR [md5] = $checksum)";
615
616         try
617         {
618             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
619             ValueFactory valueFactory = session.getValueFactory();
620             query.bindValue( "checksum", valueFactory.createValue( checksum ) );
621             QueryResult result = query.execute();
622
623             artifacts = new ArrayList<ArtifactMetadata>();
624             for ( Node n : JcrUtils.getNodes( result ) )
625             {
626                 artifacts.add( getArtifactFromNode( repositoryId, n ) );
627             }
628         }
629         catch ( RepositoryException e )
630         {
631             throw new MetadataRepositoryException( e.getMessage(), e );
632         }
633         return artifacts;
634     }
635
636     public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
637                                 String id )
638         throws MetadataRepositoryException
639     {
640         try
641         {
642             Node root = session.getRootNode();
643             String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id );
644             if ( root.hasNode( path ) )
645             {
646                 root.getNode( path ).remove();
647             }
648         }
649         catch ( RepositoryException e )
650         {
651             throw new MetadataRepositoryException( e.getMessage(), e );
652         }
653     }
654
655     public void removeRepository( String repositoryId )
656         throws MetadataRepositoryException
657     {
658         try
659         {
660             Node root = session.getRootNode();
661             String path = getRepositoryPath( repositoryId );
662             if ( root.hasNode( path ) )
663             {
664                 root.getNode( path ).remove();
665             }
666         }
667         catch ( RepositoryException e )
668         {
669             throw new MetadataRepositoryException( e.getMessage(), e );
670         }
671     }
672
673     public List<ArtifactMetadata> getArtifacts( String repositoryId )
674         throws MetadataRepositoryException
675     {
676         List<ArtifactMetadata> artifacts;
677
678         String q = getArtifactQuery( repositoryId );
679
680         try
681         {
682             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
683             QueryResult result = query.execute();
684
685             artifacts = new ArrayList<ArtifactMetadata>();
686             for ( Node n : JcrUtils.getNodes( result ) )
687             {
688                 if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
689                 {
690                     artifacts.add( getArtifactFromNode( repositoryId, n ) );
691                 }
692             }
693         }
694         catch ( RepositoryException e )
695         {
696             throw new MetadataRepositoryException( e.getMessage(), e );
697         }
698         return artifacts;
699     }
700
701     private static String getArtifactQuery( String repositoryId )
702     {
703         return "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/" +
704             getRepositoryContentPath( repositoryId ) + "')";
705     }
706
707     public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
708         throws MetadataResolutionException
709     {
710         ProjectMetadata metadata = null;
711
712         try
713         {
714             Node root = session.getRootNode();
715
716             // basically just checking it exists
717             String path = getProjectPath( repositoryId, namespace, projectId );
718             if ( root.hasNode( path ) )
719             {
720                 metadata = new ProjectMetadata();
721                 metadata.setId( projectId );
722                 metadata.setNamespace( namespace );
723             }
724         }
725         catch ( RepositoryException e )
726         {
727             throw new MetadataResolutionException( e.getMessage(), e );
728         }
729
730         return metadata;
731     }
732
733     public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
734                                                      String projectVersion )
735         throws MetadataResolutionException
736     {
737         ProjectVersionMetadata versionMetadata;
738
739         try
740         {
741             Node root = session.getRootNode();
742
743             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
744             if ( !root.hasNode( path ) )
745             {
746                 return null;
747             }
748
749             Node node = root.getNode( path );
750
751             versionMetadata = new ProjectVersionMetadata();
752             versionMetadata.setId( projectVersion );
753             versionMetadata.setName( getPropertyString( node, "name" ) );
754             versionMetadata.setDescription( getPropertyString( node, "description" ) );
755             versionMetadata.setUrl( getPropertyString( node, "url" ) );
756             versionMetadata.setIncomplete( node.hasProperty( "incomplete" ) && node.getProperty(
757                 "incomplete" ).getBoolean() );
758
759             // FIXME: decide how to treat these in the content repo
760             String scmConnection = getPropertyString( node, "scm.connection" );
761             String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
762             String scmUrl = getPropertyString( node, "scm.url" );
763             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
764             {
765                 Scm scm = new Scm();
766                 scm.setConnection( scmConnection );
767                 scm.setDeveloperConnection( scmDeveloperConnection );
768                 scm.setUrl( scmUrl );
769                 versionMetadata.setScm( scm );
770             }
771
772             String ciSystem = getPropertyString( node, "ci.system" );
773             String ciUrl = getPropertyString( node, "ci.url" );
774             if ( ciSystem != null || ciUrl != null )
775             {
776                 CiManagement ci = new CiManagement();
777                 ci.setSystem( ciSystem );
778                 ci.setUrl( ciUrl );
779                 versionMetadata.setCiManagement( ci );
780             }
781
782             String issueSystem = getPropertyString( node, "issue.system" );
783             String issueUrl = getPropertyString( node, "issue.url" );
784             if ( issueSystem != null || issueUrl != null )
785             {
786                 IssueManagement issueManagement = new IssueManagement();
787                 issueManagement.setSystem( issueSystem );
788                 issueManagement.setUrl( issueUrl );
789                 versionMetadata.setIssueManagement( issueManagement );
790             }
791
792             String orgName = getPropertyString( node, "org.name" );
793             String orgUrl = getPropertyString( node, "org.url" );
794             if ( orgName != null || orgUrl != null )
795             {
796                 Organization org = new Organization();
797                 org.setName( orgName );
798                 org.setUrl( orgUrl );
799                 versionMetadata.setOrganization( org );
800             }
801
802             boolean done = false;
803             int i = 0;
804             while ( !done )
805             {
806                 String licenseName = getPropertyString( node, "license." + i + ".name" );
807                 String licenseUrl = getPropertyString( node, "license." + i + ".url" );
808                 if ( licenseName != null || licenseUrl != null )
809                 {
810                     License license = new License();
811                     license.setName( licenseName );
812                     license.setUrl( licenseUrl );
813                     versionMetadata.addLicense( license );
814                 }
815                 else
816                 {
817                     done = true;
818                 }
819                 i++;
820             }
821
822             done = false;
823             i = 0;
824             while ( !done )
825             {
826                 String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
827                 if ( mailingListName != null )
828                 {
829                     MailingList mailingList = new MailingList();
830                     mailingList.setName( mailingListName );
831                     mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
832                     String n = "mailingList." + i + ".otherArchives";
833                     if ( node.hasProperty( n ) )
834                     {
835                         mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
836                     }
837                     else
838                     {
839                         mailingList.setOtherArchives( Collections.<String>emptyList() );
840                     }
841                     mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
842                     mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
843                     mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
844                     versionMetadata.addMailingList( mailingList );
845                 }
846                 else
847                 {
848                     done = true;
849                 }
850                 i++;
851             }
852
853             if ( node.hasNode( "dependencies" ) )
854             {
855                 Node dependenciesNode = node.getNode( "dependencies" );
856                 for ( Node n : JcrUtils.getChildNodes( dependenciesNode ) )
857                 {
858                     if ( n.isNodeType( DEPENDENCY_NODE_TYPE ) )
859                     {
860                         Dependency dependency = new Dependency();
861                         // FIXME: correct these properties
862                         dependency.setArtifactId( getPropertyString( n, "artifactId" ) );
863                         dependency.setGroupId( getPropertyString( n, "groupId" ) );
864                         dependency.setClassifier( getPropertyString( n, "classifier" ) );
865                         dependency.setOptional( Boolean.valueOf( getPropertyString( n, "optional" ) ) );
866                         dependency.setScope( getPropertyString( n, "scope" ) );
867                         dependency.setSystemPath( getPropertyString( n, "systemPath" ) );
868                         dependency.setType( getPropertyString( n, "type" ) );
869                         dependency.setVersion( getPropertyString( n, "version" ) );
870                         versionMetadata.addDependency( dependency );
871                     }
872                 }
873             }
874
875             for ( Node n : JcrUtils.getChildNodes( node ) )
876             {
877                 if ( n.isNodeType( FACET_NODE_TYPE ) )
878                 {
879                     String name = n.getName();
880                     MetadataFacetFactory factory = metadataFacetFactories.get( name );
881                     if ( factory == null )
882                     {
883                         log.error( "Attempted to load unknown project version metadata facet: " + name );
884                     }
885                     else
886                     {
887                         MetadataFacet facet = factory.createMetadataFacet();
888                         Map<String, String> map = new HashMap<String, String>();
889                         for ( Property property : JcrUtils.getProperties( n ) )
890                         {
891                             String p = property.getName();
892                             if ( !p.startsWith( "jcr:" ) )
893                             {
894                                 map.put( p, property.getString() );
895                             }
896                         }
897                         facet.fromProperties( map );
898                         versionMetadata.addFacet( facet );
899                     }
900                 }
901             }
902         }
903         catch ( RepositoryException e )
904         {
905             throw new MetadataResolutionException( e.getMessage(), e );
906         }
907
908         return versionMetadata;
909     }
910
911     public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
912                                                    String projectVersion )
913         throws MetadataResolutionException
914     {
915         Set<String> versions = new LinkedHashSet<String>();
916
917         try
918         {
919             Node root = session.getRootNode();
920
921             Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) );
922
923             for ( Node n : JcrUtils.getChildNodes( node ) )
924             {
925                 versions.add( n.getProperty( "version" ).getString() );
926             }
927         }
928         catch ( PathNotFoundException e )
929         {
930             // ignore repo not found for now
931         }
932         catch ( RepositoryException e )
933         {
934             throw new MetadataResolutionException( e.getMessage(), e );
935         }
936
937         return versions;
938     }
939
940     public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
941                                                                      String projectId, String projectVersion )
942         throws MetadataResolutionException
943     {
944         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
945
946         // TODO: bind variables instead
947         String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId +
948             "/content]) AND [groupId]='" + namespace + "' AND [artifactId]='" + projectId + "'";
949         if ( projectVersion != null )
950         {
951             q += " AND [version]='" + projectVersion + "'";
952         }
953         try
954         {
955             Query query = session.getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
956             QueryResult result = query.execute();
957
958             for ( Node n : JcrUtils.getNodes( result ) )
959             {
960                 n = n.getParent(); // dependencies grouping element
961
962                 n = n.getParent(); // project version
963                 String usedByProjectVersion = n.getName();
964
965                 n = n.getParent(); // project
966                 String usedByProject = n.getName();
967
968                 n = n.getParent(); // namespace
969                 String usedByNamespace = n.getProperty( "namespace" ).getString();
970
971                 ProjectVersionReference ref = new ProjectVersionReference();
972                 ref.setNamespace( usedByNamespace );
973                 ref.setProjectId( usedByProject );
974                 ref.setProjectVersion( usedByProjectVersion );
975                 ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY );
976                 references.add( ref );
977             }
978         }
979         catch ( RepositoryException e )
980         {
981             throw new MetadataResolutionException( e.getMessage(), e );
982         }
983
984         return references;
985     }
986
987     public Collection<String> getRootNamespaces( String repositoryId )
988         throws MetadataResolutionException
989     {
990         return getNamespaces( repositoryId, null );
991     }
992
993     public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
994         throws MetadataResolutionException
995     {
996         String path = baseNamespace != null
997             ? getNamespacePath( repositoryId, baseNamespace )
998             : getRepositoryContentPath( repositoryId );
999
1000         return getNodeNames( path, NAMESPACE_NODE_TYPE );
1001     }
1002
1003     public Collection<String> getProjects( String repositoryId, String namespace )
1004         throws MetadataResolutionException
1005     {
1006         return getNodeNames( getNamespacePath( repositoryId, namespace ), PROJECT_NODE_TYPE );
1007     }
1008
1009     public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
1010         throws MetadataResolutionException
1011     {
1012         return getNodeNames( getProjectPath( repositoryId, namespace, projectId ), PROJECT_VERSION_NODE_TYPE );
1013     }
1014
1015     public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
1016                                                       String projectVersion )
1017         throws MetadataResolutionException
1018     {
1019         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
1020
1021         try
1022         {
1023             Node root = session.getRootNode();
1024             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
1025
1026             if ( root.hasNode( path ) )
1027             {
1028                 Node node = root.getNode( path );
1029
1030                 for ( Node n : JcrUtils.getChildNodes( node ) )
1031                 {
1032                     if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1033                     {
1034                         artifacts.add( getArtifactFromNode( repositoryId, n ) );
1035                     }
1036                 }
1037             }
1038         }
1039         catch ( RepositoryException e )
1040         {
1041             throw new MetadataResolutionException( e.getMessage(), e );
1042         }
1043
1044         return artifacts;
1045     }
1046
1047     public void save()
1048         throws MetadataRepositoryException
1049     {
1050         try
1051         {
1052             session.save();
1053         }
1054         catch ( RepositoryException e )
1055         {
1056             throw new MetadataRepositoryException( e.getMessage(), e );
1057         }
1058     }
1059
1060     public void revert()
1061         throws MetadataRepositoryException
1062     {
1063         try
1064         {
1065             session.refresh( false );
1066         }
1067         catch ( RepositoryException e )
1068         {
1069             throw new MetadataRepositoryException( e.getMessage(), e );
1070         }
1071     }
1072
1073     public boolean canObtainAccess( Class<?> aClass )
1074     {
1075         return aClass == Session.class;
1076     }
1077
1078     public Object obtainAccess( Class<?> aClass )
1079     {
1080         if ( aClass == Session.class )
1081         {
1082             return session;
1083         }
1084         throw new IllegalArgumentException(
1085             "Access using " + aClass + " is not supported on the JCR metadata storage" );
1086     }
1087
1088     public void close()
1089     {
1090         session.logout();
1091     }
1092
1093     private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1094         throws RepositoryException
1095     {
1096         String id = artifactNode.getName();
1097
1098         ArtifactMetadata artifact = new ArtifactMetadata();
1099         artifact.setId( id );
1100         artifact.setRepositoryId( repositoryId );
1101
1102         Node projectVersionNode = artifactNode.getParent();
1103         Node projectNode = projectVersionNode.getParent();
1104         Node namespaceNode = projectNode.getParent();
1105
1106         artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1107         artifact.setProject( projectNode.getName() );
1108         artifact.setProjectVersion( projectVersionNode.getName() );
1109         artifact.setVersion( artifactNode.hasProperty( "version" )
1110                                  ? artifactNode.getProperty( "version" ).getString()
1111                                  : projectVersionNode.getName() );
1112
1113         if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) )
1114         {
1115             artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() );
1116         }
1117
1118         if ( artifactNode.hasProperty( "whenGathered" ) )
1119         {
1120             artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1121         }
1122
1123         if ( artifactNode.hasProperty( "size" ) )
1124         {
1125             artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1126         }
1127
1128         if ( artifactNode.hasProperty( "md5" ) )
1129         {
1130             artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1131         }
1132
1133         if ( artifactNode.hasProperty( "sha1" ) )
1134         {
1135             artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1136         }
1137
1138         for ( Node n : JcrUtils.getChildNodes( artifactNode ) )
1139         {
1140             if ( n.isNodeType( FACET_NODE_TYPE ) )
1141             {
1142                 String name = n.getName();
1143                 MetadataFacetFactory factory = metadataFacetFactories.get( name );
1144                 if ( factory == null )
1145                 {
1146                     log.error( "Attempted to load unknown project version metadata facet: " + name );
1147                 }
1148                 else
1149                 {
1150                     MetadataFacet facet = factory.createMetadataFacet();
1151                     Map<String, String> map = new HashMap<String, String>();
1152                     for ( Property p : JcrUtils.getProperties( n ) )
1153                     {
1154                         String property = p.getName();
1155                         if ( !property.startsWith( "jcr:" ) )
1156                         {
1157                             map.put( property, p.getString() );
1158                         }
1159                     }
1160                     facet.fromProperties( map );
1161                     artifact.addFacet( facet );
1162                 }
1163             }
1164         }
1165         return artifact;
1166     }
1167
1168     private static String getPropertyString( Node node, String name )
1169         throws RepositoryException
1170     {
1171         return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
1172     }
1173
1174     private Collection<String> getNodeNames( String path, String nodeType )
1175         throws MetadataResolutionException
1176     {
1177         List<String> names = new ArrayList<String>();
1178
1179         try
1180         {
1181             Node root = session.getRootNode();
1182
1183             Node nodeAtPath = root.getNode( path );
1184
1185             for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1186             {
1187                 if ( node.isNodeType( nodeType ) )
1188                 {
1189                     names.add( node.getName() );
1190                 }
1191             }
1192         }
1193         catch ( PathNotFoundException e )
1194         {
1195             // ignore repo not found for now
1196         }
1197         catch ( RepositoryException e )
1198         {
1199             throw new MetadataResolutionException( e.getMessage(), e );
1200         }
1201
1202         return names;
1203     }
1204
1205     private static String getRepositoryPath( String repositoryId )
1206     {
1207         return "repositories/" + repositoryId;
1208     }
1209
1210     private static String getRepositoryContentPath( String repositoryId )
1211     {
1212         return getRepositoryPath( repositoryId ) + "/content/";
1213     }
1214
1215     private static String getFacetPath( String repositoryId, String facetId )
1216     {
1217         return getRepositoryPath( repositoryId ) + "/facets/" + facetId;
1218     }
1219
1220     private static String getNamespacePath( String repositoryId, String namespace )
1221     {
1222         return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' );
1223     }
1224
1225     private static String getProjectPath( String repositoryId, String namespace, String projectId )
1226     {
1227         return getNamespacePath( repositoryId, namespace ) + "/" + projectId;
1228     }
1229
1230     private static String getProjectVersionPath( String repositoryId, String namespace, String projectId,
1231                                                  String projectVersion )
1232     {
1233         return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion;
1234     }
1235
1236     private static String getArtifactPath( String repositoryId, String namespace, String projectId,
1237                                            String projectVersion, String id )
1238     {
1239         return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id;
1240     }
1241
1242     private Node getOrAddNodeByPath( Node baseNode, String name )
1243         throws RepositoryException
1244     {
1245         return getOrAddNodeByPath( baseNode, name, null );
1246     }
1247
1248     private Node getOrAddNodeByPath( Node baseNode, String name, String nodeType )
1249         throws RepositoryException
1250     {
1251         Node node = baseNode;
1252         for ( String n : name.split( "/" ) )
1253         {
1254             node = JcrUtils.getOrAddNode( node, n );
1255             if ( nodeType != null )
1256             {
1257                 node.addMixin( nodeType );
1258             }
1259         }
1260         return node;
1261     }
1262
1263     private static String getFacetPath( String repositoryId, String facetId, String name )
1264     {
1265         return getFacetPath( repositoryId, facetId ) + "/" + name;
1266     }
1267
1268     private Node getOrAddRepositoryNode( String repositoryId )
1269         throws RepositoryException
1270     {
1271         Node root = session.getRootNode();
1272         Node node = JcrUtils.getOrAddNode( root, "repositories" );
1273         node = JcrUtils.getOrAddNode( node, repositoryId );
1274         return node;
1275     }
1276
1277     private Node getOrAddRepositoryContentNode( String repositoryId )
1278         throws RepositoryException
1279     {
1280         Node node = getOrAddRepositoryNode( repositoryId );
1281         return JcrUtils.getOrAddNode( node, "content" );
1282     }
1283
1284     private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1285         throws RepositoryException
1286     {
1287         Node repo = getOrAddRepositoryContentNode( repositoryId );
1288         return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ), NAMESPACE_NODE_TYPE );
1289     }
1290
1291     private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1292         throws RepositoryException
1293     {
1294         Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1295         Node node = JcrUtils.getOrAddNode( namespaceNode, projectId );
1296         node.addMixin( PROJECT_NODE_TYPE );
1297         return node;
1298     }
1299
1300     private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1301                                              String projectVersion )
1302         throws RepositoryException
1303     {
1304         Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1305         Node node = JcrUtils.getOrAddNode( projectNode, projectVersion );
1306         node.addMixin( PROJECT_VERSION_NODE_TYPE );
1307         return node;
1308     }
1309
1310     private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1311                                        String id )
1312         throws RepositoryException
1313     {
1314         Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1315         Node node = JcrUtils.getOrAddNode( versionNode, id );
1316         node.addMixin( ARTIFACT_NODE_TYPE );
1317         return node;
1318     }
1319
1320     private static Calendar createCalendar( Date time )
1321     {
1322         Calendar cal = Calendar.getInstance();
1323         cal.setTime( time );
1324         return cal;
1325     }
1326
1327     private String join( Collection<String> ids )
1328     {
1329         if ( ids != null && !ids.isEmpty() )
1330         {
1331             StringBuilder s = new StringBuilder();
1332             for ( String id : ids )
1333             {
1334                 s.append( id );
1335                 s.append( "," );
1336             }
1337             return s.substring( 0, s.length() - 1 );
1338         }
1339         return null;
1340     }
1341
1342     public Session getJcrSession()
1343     {
1344         return session;
1345     }
1346 }