]> source.dussan.org Git - archiva.git/blob
0ad272528c3ec3212a24be39c8ae053095e8af13
[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             if ( metadataFacetFactories == null )
417             {
418                 return metadataFacet;
419             }
420
421             MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId );
422             if ( metadataFacetFactory != null )
423             {
424                 metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name );
425                 Map<String, String> map = new HashMap<String, String>();
426                 for ( Property property : JcrUtils.getProperties( node ) )
427                 {
428                     String p = property.getName();
429                     if ( !p.startsWith( "jcr:" ) )
430                     {
431                         map.put( p, property.getString() );
432                     }
433                 }
434                 metadataFacet.fromProperties( map );
435             }
436         }
437         catch ( PathNotFoundException e )
438         {
439             // ignored - the facet doesn't exist, so return null
440         }
441         catch ( RepositoryException e )
442         {
443             throw new MetadataRepositoryException( e.getMessage(), e );
444         }
445         return metadataFacet;
446     }
447
448     public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet )
449         throws MetadataRepositoryException
450     {
451         try
452         {
453             Node repo = getOrAddRepositoryNode( repositoryId );
454             Node facets = JcrUtils.getOrAddNode( repo, "facets" );
455
456             String id = metadataFacet.getFacetId();
457             Node facetNode = JcrUtils.getOrAddNode( facets, id );
458
459             Node node = getOrAddNodeByPath( facetNode, metadataFacet.getName() );
460
461             for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() )
462             {
463                 node.setProperty( entry.getKey(), entry.getValue() );
464             }
465         }
466         catch ( RepositoryException e )
467         {
468             throw new MetadataRepositoryException( e.getMessage(), e );
469         }
470     }
471
472     public void removeMetadataFacets( String repositoryId, String facetId )
473         throws MetadataRepositoryException
474     {
475         try
476         {
477             Node root = getJcrSession().getRootNode();
478             String path = getFacetPath( repositoryId, facetId );
479             if ( root.hasNode( path ) )
480             {
481                 root.getNode( path ).remove();
482             }
483         }
484         catch ( RepositoryException e )
485         {
486             throw new MetadataRepositoryException( e.getMessage(), e );
487         }
488     }
489
490     public void removeMetadataFacet( String repositoryId, String facetId, String name )
491         throws MetadataRepositoryException
492     {
493         try
494         {
495             Node root = getJcrSession().getRootNode();
496             String path = getFacetPath( repositoryId, facetId, name );
497             if ( root.hasNode( path ) )
498             {
499                 Node node = root.getNode( path );
500                 do
501                 {
502                     // also remove empty container nodes
503                     Node parent = node.getParent();
504                     node.remove();
505                     node = parent;
506                 }
507                 while ( !node.hasNodes() );
508             }
509         }
510         catch ( RepositoryException e )
511         {
512             throw new MetadataRepositoryException( e.getMessage(), e );
513         }
514     }
515
516     public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime )
517         throws MetadataRepositoryException
518     {
519         List<ArtifactMetadata> artifacts;
520
521         String q = getArtifactQuery( repoId );
522
523         if ( startTime != null )
524         {
525             q += " AND [whenGathered] >= $start";
526         }
527         if ( endTime != null )
528         {
529             q += " AND [whenGathered] <= $end";
530         }
531
532         try
533         {
534             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
535             ValueFactory valueFactory = getJcrSession().getValueFactory();
536             if ( startTime != null )
537             {
538                 query.bindValue( "start", valueFactory.createValue( createCalendar( startTime ) ) );
539             }
540             if ( endTime != null )
541             {
542                 query.bindValue( "end", valueFactory.createValue( createCalendar( endTime ) ) );
543             }
544             QueryResult result = query.execute();
545
546             artifacts = new ArrayList<ArtifactMetadata>();
547             for ( Node n : JcrUtils.getNodes( result ) )
548             {
549                 artifacts.add( getArtifactFromNode( repoId, n ) );
550             }
551         }
552         catch ( RepositoryException e )
553         {
554             throw new MetadataRepositoryException( e.getMessage(), e );
555         }
556         return artifacts;
557     }
558
559     public Collection<String> getRepositories()
560         throws MetadataRepositoryException
561     {
562         List<String> repositories;
563
564         try
565         {
566             Node root = getJcrSession().getRootNode();
567             if ( root.hasNode( "repositories" ) )
568             {
569                 Node node = root.getNode( "repositories" );
570
571                 repositories = new ArrayList<String>();
572                 NodeIterator i = node.getNodes();
573                 while ( i.hasNext() )
574                 {
575                     Node n = i.nextNode();
576                     repositories.add( n.getName() );
577                 }
578             }
579             else
580             {
581                 repositories = Collections.emptyList();
582             }
583         }
584         catch ( RepositoryException e )
585         {
586             throw new MetadataRepositoryException( e.getMessage(), e );
587         }
588         return repositories;
589     }
590
591     public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum )
592         throws MetadataRepositoryException
593     {
594         List<ArtifactMetadata> artifacts;
595
596         String q = getArtifactQuery( repositoryId ) + " AND ([sha1] = $checksum OR [md5] = $checksum)";
597
598         try
599         {
600             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
601             ValueFactory valueFactory = getJcrSession().getValueFactory();
602             query.bindValue( "checksum", valueFactory.createValue( checksum ) );
603             QueryResult result = query.execute();
604
605             artifacts = new ArrayList<ArtifactMetadata>();
606             for ( Node n : JcrUtils.getNodes( result ) )
607             {
608                 artifacts.add( getArtifactFromNode( repositoryId, n ) );
609             }
610         }
611         catch ( RepositoryException e )
612         {
613             throw new MetadataRepositoryException( e.getMessage(), e );
614         }
615         return artifacts;
616     }
617
618     public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion,
619                                 String id )
620         throws MetadataRepositoryException
621     {
622         try
623         {
624             Node root = getJcrSession().getRootNode();
625             String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id );
626             if ( root.hasNode( path ) )
627             {
628                 root.getNode( path ).remove();
629             }
630         }
631         catch ( RepositoryException e )
632         {
633             throw new MetadataRepositoryException( e.getMessage(), e );
634         }
635     }
636
637     public void removeRepository( String repositoryId )
638         throws MetadataRepositoryException
639     {
640         try
641         {
642             Node root = getJcrSession().getRootNode();
643             String path = getRepositoryPath( repositoryId );
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 List<ArtifactMetadata> getArtifacts( String repositoryId )
656         throws MetadataRepositoryException
657     {
658         List<ArtifactMetadata> artifacts;
659
660         String q = getArtifactQuery( repositoryId );
661
662         try
663         {
664             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
665             QueryResult result = query.execute();
666
667             artifacts = new ArrayList<ArtifactMetadata>();
668             for ( Node n : JcrUtils.getNodes( result ) )
669             {
670                 if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
671                 {
672                     artifacts.add( getArtifactFromNode( repositoryId, n ) );
673                 }
674             }
675         }
676         catch ( RepositoryException e )
677         {
678             throw new MetadataRepositoryException( e.getMessage(), e );
679         }
680         return artifacts;
681     }
682
683     private static String getArtifactQuery( String repositoryId )
684     {
685         return "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/" +
686             getRepositoryContentPath( repositoryId ) + "')";
687     }
688
689     public ProjectMetadata getProject( String repositoryId, String namespace, String projectId )
690         throws MetadataResolutionException
691     {
692         ProjectMetadata metadata = null;
693
694         try
695         {
696             Node root = getJcrSession().getRootNode();
697
698             // basically just checking it exists
699             String path = getProjectPath( repositoryId, namespace, projectId );
700             if ( root.hasNode( path ) )
701             {
702                 metadata = new ProjectMetadata();
703                 metadata.setId( projectId );
704                 metadata.setNamespace( namespace );
705             }
706         }
707         catch ( RepositoryException e )
708         {
709             throw new MetadataResolutionException( e.getMessage(), e );
710         }
711
712         return metadata;
713     }
714
715     public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId,
716                                                      String projectVersion )
717         throws MetadataResolutionException
718     {
719         ProjectVersionMetadata versionMetadata;
720
721         try
722         {
723             Node root = getJcrSession().getRootNode();
724
725             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
726             if ( !root.hasNode( path ) )
727             {
728                 return null;
729             }
730
731             Node node = root.getNode( path );
732
733             versionMetadata = new ProjectVersionMetadata();
734             versionMetadata.setId( projectVersion );
735             versionMetadata.setName( getPropertyString( node, "name" ) );
736             versionMetadata.setDescription( getPropertyString( node, "description" ) );
737             versionMetadata.setUrl( getPropertyString( node, "url" ) );
738             versionMetadata.setIncomplete(
739                 node.hasProperty( "incomplete" ) && node.getProperty( "incomplete" ).getBoolean() );
740
741             // FIXME: decide how to treat these in the content repo
742             String scmConnection = getPropertyString( node, "scm.connection" );
743             String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" );
744             String scmUrl = getPropertyString( node, "scm.url" );
745             if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null )
746             {
747                 Scm scm = new Scm();
748                 scm.setConnection( scmConnection );
749                 scm.setDeveloperConnection( scmDeveloperConnection );
750                 scm.setUrl( scmUrl );
751                 versionMetadata.setScm( scm );
752             }
753
754             String ciSystem = getPropertyString( node, "ci.system" );
755             String ciUrl = getPropertyString( node, "ci.url" );
756             if ( ciSystem != null || ciUrl != null )
757             {
758                 CiManagement ci = new CiManagement();
759                 ci.setSystem( ciSystem );
760                 ci.setUrl( ciUrl );
761                 versionMetadata.setCiManagement( ci );
762             }
763
764             String issueSystem = getPropertyString( node, "issue.system" );
765             String issueUrl = getPropertyString( node, "issue.url" );
766             if ( issueSystem != null || issueUrl != null )
767             {
768                 IssueManagement issueManagement = new IssueManagement();
769                 issueManagement.setSystem( issueSystem );
770                 issueManagement.setUrl( issueUrl );
771                 versionMetadata.setIssueManagement( issueManagement );
772             }
773
774             String orgName = getPropertyString( node, "org.name" );
775             String orgUrl = getPropertyString( node, "org.url" );
776             if ( orgName != null || orgUrl != null )
777             {
778                 Organization org = new Organization();
779                 org.setName( orgName );
780                 org.setUrl( orgUrl );
781                 versionMetadata.setOrganization( org );
782             }
783
784             boolean done = false;
785             int i = 0;
786             while ( !done )
787             {
788                 String licenseName = getPropertyString( node, "license." + i + ".name" );
789                 String licenseUrl = getPropertyString( node, "license." + i + ".url" );
790                 if ( licenseName != null || licenseUrl != null )
791                 {
792                     License license = new License();
793                     license.setName( licenseName );
794                     license.setUrl( licenseUrl );
795                     versionMetadata.addLicense( license );
796                 }
797                 else
798                 {
799                     done = true;
800                 }
801                 i++;
802             }
803
804             done = false;
805             i = 0;
806             while ( !done )
807             {
808                 String mailingListName = getPropertyString( node, "mailingList." + i + ".name" );
809                 if ( mailingListName != null )
810                 {
811                     MailingList mailingList = new MailingList();
812                     mailingList.setName( mailingListName );
813                     mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) );
814                     String n = "mailingList." + i + ".otherArchives";
815                     if ( node.hasProperty( n ) )
816                     {
817                         mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) );
818                     }
819                     else
820                     {
821                         mailingList.setOtherArchives( Collections.<String>emptyList() );
822                     }
823                     mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) );
824                     mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) );
825                     mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) );
826                     versionMetadata.addMailingList( mailingList );
827                 }
828                 else
829                 {
830                     done = true;
831                 }
832                 i++;
833             }
834
835             if ( node.hasNode( "dependencies" ) )
836             {
837                 Node dependenciesNode = node.getNode( "dependencies" );
838                 for ( Node n : JcrUtils.getChildNodes( dependenciesNode ) )
839                 {
840                     if ( n.isNodeType( DEPENDENCY_NODE_TYPE ) )
841                     {
842                         Dependency dependency = new Dependency();
843                         // FIXME: correct these properties
844                         dependency.setArtifactId( getPropertyString( n, "artifactId" ) );
845                         dependency.setGroupId( getPropertyString( n, "groupId" ) );
846                         dependency.setClassifier( getPropertyString( n, "classifier" ) );
847                         dependency.setOptional( Boolean.valueOf( getPropertyString( n, "optional" ) ) );
848                         dependency.setScope( getPropertyString( n, "scope" ) );
849                         dependency.setSystemPath( getPropertyString( n, "systemPath" ) );
850                         dependency.setType( getPropertyString( n, "type" ) );
851                         dependency.setVersion( getPropertyString( n, "version" ) );
852                         versionMetadata.addDependency( dependency );
853                     }
854                 }
855             }
856
857             for ( Node n : JcrUtils.getChildNodes( node ) )
858             {
859                 if ( n.isNodeType( FACET_NODE_TYPE ) )
860                 {
861                     String name = n.getName();
862                     MetadataFacetFactory factory = metadataFacetFactories.get( name );
863                     if ( factory == null )
864                     {
865                         log.error( "Attempted to load unknown project version metadata facet: {}", name );
866                     }
867                     else
868                     {
869                         MetadataFacet facet = factory.createMetadataFacet();
870                         Map<String, String> map = new HashMap<String, String>();
871                         for ( Property property : JcrUtils.getProperties( n ) )
872                         {
873                             String p = property.getName();
874                             if ( !p.startsWith( "jcr:" ) )
875                             {
876                                 map.put( p, property.getString() );
877                             }
878                         }
879                         facet.fromProperties( map );
880                         versionMetadata.addFacet( facet );
881                     }
882                 }
883             }
884         }
885         catch ( RepositoryException e )
886         {
887             throw new MetadataResolutionException( e.getMessage(), e );
888         }
889
890         return versionMetadata;
891     }
892
893     public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId,
894                                                    String projectVersion )
895         throws MetadataResolutionException
896     {
897         Set<String> versions = new LinkedHashSet<String>();
898
899         try
900         {
901             Node root = getJcrSession().getRootNode();
902
903             Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) );
904
905             for ( Node n : JcrUtils.getChildNodes( node ) )
906             {
907                 versions.add( n.getProperty( "version" ).getString() );
908             }
909         }
910         catch ( PathNotFoundException e )
911         {
912             // ignore repo not found for now
913         }
914         catch ( RepositoryException e )
915         {
916             throw new MetadataResolutionException( e.getMessage(), e );
917         }
918
919         return versions;
920     }
921
922     public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace,
923                                                                      String projectId, String projectVersion )
924         throws MetadataResolutionException
925     {
926         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
927
928         // TODO: bind variables instead
929         String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId +
930             "/content]) AND [groupId]='" + namespace + "' AND [artifactId]='" + projectId + "'";
931         if ( projectVersion != null )
932         {
933             q += " AND [version]='" + projectVersion + "'";
934         }
935         try
936         {
937             Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 );
938             QueryResult result = query.execute();
939
940             for ( Node n : JcrUtils.getNodes( result ) )
941             {
942                 n = n.getParent(); // dependencies grouping element
943
944                 n = n.getParent(); // project version
945                 String usedByProjectVersion = n.getName();
946
947                 n = n.getParent(); // project
948                 String usedByProject = n.getName();
949
950                 n = n.getParent(); // namespace
951                 String usedByNamespace = n.getProperty( "namespace" ).getString();
952
953                 ProjectVersionReference ref = new ProjectVersionReference();
954                 ref.setNamespace( usedByNamespace );
955                 ref.setProjectId( usedByProject );
956                 ref.setProjectVersion( usedByProjectVersion );
957                 ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY );
958                 references.add( ref );
959             }
960         }
961         catch ( RepositoryException e )
962         {
963             throw new MetadataResolutionException( e.getMessage(), e );
964         }
965
966         return references;
967     }
968
969     public Collection<String> getRootNamespaces( String repositoryId )
970         throws MetadataResolutionException
971     {
972         return getNamespaces( repositoryId, null );
973     }
974
975     public Collection<String> getNamespaces( String repositoryId, String baseNamespace )
976         throws MetadataResolutionException
977     {
978         String path = baseNamespace != null
979             ? getNamespacePath( repositoryId, baseNamespace )
980             : getRepositoryContentPath( repositoryId );
981
982         return getNodeNames( path, NAMESPACE_NODE_TYPE );
983     }
984
985     public Collection<String> getProjects( String repositoryId, String namespace )
986         throws MetadataResolutionException
987     {
988         return getNodeNames( getNamespacePath( repositoryId, namespace ), PROJECT_NODE_TYPE );
989     }
990
991     public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId )
992         throws MetadataResolutionException
993     {
994         return getNodeNames( getProjectPath( repositoryId, namespace, projectId ), PROJECT_VERSION_NODE_TYPE );
995     }
996
997     public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId,
998                                                       String projectVersion )
999         throws MetadataResolutionException
1000     {
1001         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
1002
1003         try
1004         {
1005             Node root = getJcrSession().getRootNode();
1006             String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion );
1007
1008             if ( root.hasNode( path ) )
1009             {
1010                 Node node = root.getNode( path );
1011
1012                 for ( Node n : JcrUtils.getChildNodes( node ) )
1013                 {
1014                     if ( n.isNodeType( ARTIFACT_NODE_TYPE ) )
1015                     {
1016                         artifacts.add( getArtifactFromNode( repositoryId, n ) );
1017                     }
1018                 }
1019             }
1020         }
1021         catch ( RepositoryException e )
1022         {
1023             throw new MetadataResolutionException( e.getMessage(), e );
1024         }
1025
1026         return artifacts;
1027     }
1028
1029     public void save()
1030     {
1031         try
1032         {
1033             getJcrSession().save();
1034         }
1035         catch ( RepositoryException e )
1036         {
1037             throw new RuntimeException( e.getMessage(), e );
1038         }
1039     }
1040
1041     public void revert()
1042     {
1043         try
1044         {
1045             getJcrSession().refresh( false );
1046         }
1047         catch ( RepositoryException e )
1048         {
1049             throw new RuntimeException( e.getMessage(), e );
1050         }
1051     }
1052
1053     public boolean canObtainAccess( Class<?> aClass )
1054     {
1055         return aClass == Session.class;
1056     }
1057
1058     public Object obtainAccess( Class<?> aClass )
1059         throws MetadataRepositoryException
1060     {
1061         if ( aClass == Session.class )
1062         {
1063             try
1064             {
1065                 return getJcrSession();
1066             }
1067             catch ( RepositoryException e )
1068             {
1069                 log.error( e.getMessage(), e );
1070                 throw new MetadataRepositoryException( e.getMessage(), e );
1071             }
1072         }
1073         throw new IllegalArgumentException(
1074             "Access using " + aClass + " is not supported on the JCR metadata storage" );
1075     }
1076
1077     public void close()
1078         throws MetadataRepositoryException
1079     {
1080         try
1081         {
1082             if ( getJcrSession().isLive() )
1083             {
1084                 getJcrSession().logout();
1085             }
1086         }
1087         catch ( RepositoryException e )
1088         {
1089             log.error( e.getMessage(), e );
1090             throw new MetadataRepositoryException( e.getMessage(), e );
1091         }
1092     }
1093
1094     private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode )
1095         throws RepositoryException
1096     {
1097         String id = artifactNode.getName();
1098
1099         ArtifactMetadata artifact = new ArtifactMetadata();
1100         artifact.setId( id );
1101         artifact.setRepositoryId( repositoryId );
1102
1103         Node projectVersionNode = artifactNode.getParent();
1104         Node projectNode = projectVersionNode.getParent();
1105         Node namespaceNode = projectNode.getParent();
1106
1107         artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() );
1108         artifact.setProject( projectNode.getName() );
1109         artifact.setProjectVersion( projectVersionNode.getName() );
1110         artifact.setVersion( artifactNode.hasProperty( "version" )
1111                                  ? artifactNode.getProperty( "version" ).getString()
1112                                  : projectVersionNode.getName() );
1113
1114         if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) )
1115         {
1116             artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() );
1117         }
1118
1119         if ( artifactNode.hasProperty( "whenGathered" ) )
1120         {
1121             artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() );
1122         }
1123
1124         if ( artifactNode.hasProperty( "size" ) )
1125         {
1126             artifact.setSize( artifactNode.getProperty( "size" ).getLong() );
1127         }
1128
1129         if ( artifactNode.hasProperty( "md5" ) )
1130         {
1131             artifact.setMd5( artifactNode.getProperty( "md5" ).getString() );
1132         }
1133
1134         if ( artifactNode.hasProperty( "sha1" ) )
1135         {
1136             artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() );
1137         }
1138
1139         for ( Node n : JcrUtils.getChildNodes( artifactNode ) )
1140         {
1141             if ( n.isNodeType( FACET_NODE_TYPE ) )
1142             {
1143                 String name = n.getName();
1144                 MetadataFacetFactory factory = metadataFacetFactories.get( name );
1145                 if ( factory == null )
1146                 {
1147                     log.error( "Attempted to load unknown project version metadata facet: " + name );
1148                 }
1149                 else
1150                 {
1151                     MetadataFacet facet = factory.createMetadataFacet();
1152                     Map<String, String> map = new HashMap<String, String>();
1153                     for ( Property p : JcrUtils.getProperties( n ) )
1154                     {
1155                         String property = p.getName();
1156                         if ( !property.startsWith( "jcr:" ) )
1157                         {
1158                             map.put( property, p.getString() );
1159                         }
1160                     }
1161                     facet.fromProperties( map );
1162                     artifact.addFacet( facet );
1163                 }
1164             }
1165         }
1166         return artifact;
1167     }
1168
1169     private static String getPropertyString( Node node, String name )
1170         throws RepositoryException
1171     {
1172         return node.hasProperty( name ) ? node.getProperty( name ).getString() : null;
1173     }
1174
1175     private Collection<String> getNodeNames( String path, String nodeType )
1176         throws MetadataResolutionException
1177     {
1178         List<String> names = new ArrayList<String>();
1179
1180         try
1181         {
1182             Node root = getJcrSession().getRootNode();
1183
1184             Node nodeAtPath = root.getNode( path );
1185
1186             for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) )
1187             {
1188                 if ( node.isNodeType( nodeType ) )
1189                 {
1190                     names.add( node.getName() );
1191                 }
1192             }
1193         }
1194         catch ( PathNotFoundException e )
1195         {
1196             // ignore repo not found for now
1197         }
1198         catch ( RepositoryException e )
1199         {
1200             throw new MetadataResolutionException( e.getMessage(), e );
1201         }
1202
1203         return names;
1204     }
1205
1206     private static String getRepositoryPath( String repositoryId )
1207     {
1208         return "repositories/" + repositoryId;
1209     }
1210
1211     private static String getRepositoryContentPath( String repositoryId )
1212     {
1213         return getRepositoryPath( repositoryId ) + "/content/";
1214     }
1215
1216     private static String getFacetPath( String repositoryId, String facetId )
1217     {
1218         return getRepositoryPath( repositoryId ) + "/facets/" + facetId;
1219     }
1220
1221     private static String getNamespacePath( String repositoryId, String namespace )
1222     {
1223         return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' );
1224     }
1225
1226     private static String getProjectPath( String repositoryId, String namespace, String projectId )
1227     {
1228         return getNamespacePath( repositoryId, namespace ) + "/" + projectId;
1229     }
1230
1231     private static String getProjectVersionPath( String repositoryId, String namespace, String projectId,
1232                                                  String projectVersion )
1233     {
1234         return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion;
1235     }
1236
1237     private static String getArtifactPath( String repositoryId, String namespace, String projectId,
1238                                            String projectVersion, String id )
1239     {
1240         return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id;
1241     }
1242
1243     private Node getOrAddNodeByPath( Node baseNode, String name )
1244         throws RepositoryException
1245     {
1246         return getOrAddNodeByPath( baseNode, name, null );
1247     }
1248
1249     private Node getOrAddNodeByPath( Node baseNode, String name, String nodeType )
1250         throws RepositoryException
1251     {
1252         Node node = baseNode;
1253         for ( String n : name.split( "/" ) )
1254         {
1255             node = JcrUtils.getOrAddNode( node, n );
1256             if ( nodeType != null )
1257             {
1258                 node.addMixin( nodeType );
1259             }
1260         }
1261         return node;
1262     }
1263
1264     private static String getFacetPath( String repositoryId, String facetId, String name )
1265     {
1266         return getFacetPath( repositoryId, facetId ) + "/" + name;
1267     }
1268
1269     private Node getOrAddRepositoryNode( String repositoryId )
1270         throws RepositoryException
1271     {
1272         Node root = getJcrSession().getRootNode();
1273         Node node = JcrUtils.getOrAddNode( root, "repositories" );
1274         node = JcrUtils.getOrAddNode( node, repositoryId );
1275         return node;
1276     }
1277
1278     private Node getOrAddRepositoryContentNode( String repositoryId )
1279         throws RepositoryException
1280     {
1281         Node node = getOrAddRepositoryNode( repositoryId );
1282         return JcrUtils.getOrAddNode( node, "content" );
1283     }
1284
1285     private Node getOrAddNamespaceNode( String repositoryId, String namespace )
1286         throws RepositoryException
1287     {
1288         Node repo = getOrAddRepositoryContentNode( repositoryId );
1289         return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ), NAMESPACE_NODE_TYPE );
1290     }
1291
1292     private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId )
1293         throws RepositoryException
1294     {
1295         Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace );
1296         Node node = JcrUtils.getOrAddNode( namespaceNode, projectId );
1297         node.addMixin( PROJECT_NODE_TYPE );
1298         return node;
1299     }
1300
1301     private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId,
1302                                              String projectVersion )
1303         throws RepositoryException
1304     {
1305         Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId );
1306         Node node = JcrUtils.getOrAddNode( projectNode, projectVersion );
1307         node.addMixin( PROJECT_VERSION_NODE_TYPE );
1308         return node;
1309     }
1310
1311     private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion,
1312                                        String id )
1313         throws RepositoryException
1314     {
1315         Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion );
1316         Node node = JcrUtils.getOrAddNode( versionNode, id );
1317         node.addMixin( ARTIFACT_NODE_TYPE );
1318         return node;
1319     }
1320
1321     private static Calendar createCalendar( Date time )
1322     {
1323         Calendar cal = Calendar.getInstance();
1324         cal.setTime( time );
1325         return cal;
1326     }
1327
1328     private String join( Collection<String> ids )
1329     {
1330         if ( ids != null && !ids.isEmpty() )
1331         {
1332             StringBuilder s = new StringBuilder();
1333             for ( String id : ids )
1334             {
1335                 s.append( id );
1336                 s.append( "," );
1337             }
1338             return s.substring( 0, s.length() - 1 );
1339         }
1340         return null;
1341     }
1342
1343     public Session getJcrSession()
1344         throws RepositoryException
1345     {
1346         if ( this.jcrSession == null || !this.jcrSession.isLive() )
1347         {
1348
1349             jcrSession = repository.login( new SimpleCredentials( "admin", "admin".toCharArray() ) );
1350
1351         }
1352         return this.jcrSession;
1353     }
1354 }