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