]> source.dussan.org Git - archiva.git/blob
30e134ff384fcbec9b6c7e8657d8cbef60ecb6a5
[archiva.git] /
1 package org.apache.maven.archiva.web.action;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import com.opensymphony.xwork2.Validateable;
23 import org.apache.archiva.metadata.generic.GenericMetadataFacet;
24 import org.apache.archiva.metadata.model.ArtifactMetadata;
25 import org.apache.archiva.metadata.model.Dependency;
26 import org.apache.archiva.metadata.model.MailingList;
27 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
28 import org.apache.archiva.metadata.model.ProjectVersionReference;
29 import org.apache.archiva.metadata.repository.MetadataRepository;
30 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
31 import org.apache.archiva.metadata.repository.MetadataResolutionException;
32 import org.apache.archiva.metadata.repository.MetadataResolver;
33 import org.apache.archiva.metadata.repository.RepositorySession;
34 import org.apache.archiva.metadata.repository.storage.maven2.MavenArtifactFacet;
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.maven.archiva.model.ArtifactReference;
37 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
38 import org.apache.maven.archiva.repository.RepositoryContentFactory;
39 import org.apache.maven.archiva.repository.RepositoryException;
40 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
41 import org.springframework.context.annotation.Scope;
42 import org.springframework.stereotype.Controller;
43
44 import java.text.DecimalFormat;
45 import java.text.DecimalFormatSymbols;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.HashMap;
51 import java.util.LinkedHashMap;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.Map;
55
56 /**
57  * Browse the repository.
58  *
59  * TODO change name to ShowVersionedAction to conform to terminology.
60  *
61  * plexus.component role="com.opensymphony.xwork2.Action" role-hint="showArtifactAction"
62  * instantiation-strategy="per-lookup"
63  */
64 @SuppressWarnings( "serial" )
65 @Controller( "showArtifactAction" )
66 @Scope( "prototype" )
67 public class ShowArtifactAction
68     extends AbstractRepositoryBasedAction
69     implements Validateable
70 {
71     /* .\ Not Exposed \._____________________________________________ */
72
73     /**
74      * plexus.requirement
75      */
76     private RepositoryContentFactory repositoryFactory;
77
78     /* .\ Exposed Output Objects \.__________________________________ */
79
80     private String groupId;
81
82     private String artifactId;
83
84     private String version;
85
86     private String repositoryId;
87
88     /**
89      * The model of this versioned project.
90      */
91     private ProjectVersionMetadata model;
92
93     /**
94      * The list of artifacts that depend on this versioned project.
95      */
96     private List<ProjectVersionReference> dependees;
97
98     private List<MailingList> mailingLists;
99
100     private List<Dependency> dependencies;
101
102     private Map<String, List<ArtifactDownloadInfo>> artifacts;
103
104     private boolean dependencyTree = false;
105
106     private String deleteItem;
107
108     private Map<String, String> genericMetadata;
109
110     private String propertyName;
111
112     private String propertyValue;
113
114     /**
115      * Show the versioned project information tab. TODO: Change name to 'project' - we are showing project versions
116      * here, not specific artifact information (though that is rendered in the download box).
117      */
118     public String artifact()
119     {
120         RepositorySession repositorySession = repositorySessionFactory.createSession();
121         try
122         {
123             return handleArtifact( repositorySession );
124         }
125         finally
126         {
127             repositorySession.close();
128         }
129     }
130
131     private String handleArtifact( RepositorySession session )
132     {
133         // In the future, this should be replaced by the repository grouping mechanism, so that we are only making
134         // simple resource requests here and letting the resolver take care of it
135         ProjectVersionMetadata versionMetadata = getProjectVersionMetadata( session );
136
137         if ( versionMetadata == null )
138         {
139             addActionError( "Artifact not found" );
140             return ERROR;
141         }
142
143         if ( versionMetadata.isIncomplete() )
144         {
145             addIncompleteModelWarning();
146         }
147
148         model = versionMetadata;
149
150         return SUCCESS;
151     }
152
153     private ProjectVersionMetadata getProjectVersionMetadata( RepositorySession session )
154     {
155         ProjectVersionMetadata versionMetadata = null;
156         artifacts = new LinkedHashMap<String, List<ArtifactDownloadInfo>>();
157
158         List<String> repos = getObservableRepos();
159
160         MetadataResolver metadataResolver = session.getResolver();
161         for ( String repoId : repos )
162         {
163             if ( versionMetadata == null )
164             {
165                 // we don't want the implementation being that intelligent - so another resolver to do the
166                 // "just-in-time" nature of picking up the metadata (if appropriate for the repository type) is used
167                 try
168                 {
169                     versionMetadata = metadataResolver.resolveProjectVersion( session, repoId, groupId, artifactId,
170                                                                               version );
171                 }
172                 catch ( MetadataResolutionException e )
173                 {
174                     addIncompleteModelWarning();
175
176                     // TODO: need a consistent way to construct this - same in ArchivaMetadataCreationConsumer
177                     versionMetadata = new ProjectVersionMetadata();
178                     versionMetadata.setId( version );
179                 }
180                 if ( versionMetadata != null )
181                 {
182                     repositoryId = repoId;
183
184                     List<ArtifactMetadata> artifacts;
185                     try
186                     {
187                         artifacts = new ArrayList<ArtifactMetadata>( metadataResolver.resolveArtifacts( session, repoId,
188                                                                                                         groupId,
189                                                                                                         artifactId,
190                                                                                                         version ) );
191                     }
192                     catch ( MetadataResolutionException e )
193                     {
194                         addIncompleteModelWarning();
195
196                         artifacts = Collections.emptyList();
197                     }
198                     Collections.sort( artifacts, new Comparator<ArtifactMetadata>()
199                     {
200                         public int compare( ArtifactMetadata o1, ArtifactMetadata o2 )
201                         {
202                             // sort by version (reverse), then ID
203                             // TODO: move version sorting into repository handling (maven2 specific), and perhaps add a
204                             // way to get latest instead
205                             int result = new DefaultArtifactVersion( o2.getVersion() ).compareTo(
206                                 new DefaultArtifactVersion( o1.getVersion() ) );
207                             return result != 0 ? result : o1.getId().compareTo( o2.getId() );
208                         }
209                     } );
210
211                     for ( ArtifactMetadata artifact : artifacts )
212                     {
213                         List<ArtifactDownloadInfo> l = this.artifacts.get( artifact.getVersion() );
214                         if ( l == null )
215                         {
216                             l = new ArrayList<ArtifactDownloadInfo>();
217                             this.artifacts.put( artifact.getVersion(), l );
218                         }
219                         l.add( new ArtifactDownloadInfo( artifact ) );
220                     }
221                 }
222             }
223         }
224
225         return versionMetadata;
226     }
227
228     private void addIncompleteModelWarning()
229     {
230         addActionMessage(
231             "The model may be incomplete due to a previous error in resolving information. Refer to the repository problem reports for more information." );
232     }
233
234     /**
235      * Show the artifact information tab.
236      */
237     public String dependencies()
238     {
239         String result = artifact();
240
241         this.dependencies = model.getDependencies();
242
243         return result;
244     }
245
246     /**
247      * Show the mailing lists information tab.
248      */
249     public String mailingLists()
250     {
251         String result = artifact();
252
253         this.mailingLists = model.getMailingLists();
254
255         return result;
256     }
257
258     /**
259      * Show the reports tab.
260      */
261     public String reports()
262     {
263         // TODO: hook up reports on project
264
265         return SUCCESS;
266     }
267
268     /**
269      * Show the dependees (other artifacts that depend on this project) tab.
270      */
271     public String dependees()
272         throws MetadataResolutionException
273     {
274         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
275         // TODO: what if we get duplicates across repositories?
276         RepositorySession repositorySession = repositorySessionFactory.createSession();
277         try
278         {
279             MetadataResolver metadataResolver = repositorySession.getResolver();
280             for ( String repoId : getObservableRepos() )
281             {
282                 // TODO: what about if we want to see this irrespective of version?
283                 references.addAll( metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId,
284                                                                               artifactId, version ) );
285             }
286         }
287         finally
288         {
289             repositorySession.close();
290         }
291
292         this.dependees = references;
293
294         // TODO: may need to note on the page that references will be incomplete if the other artifacts are not yet
295         // stored in the content repository
296         // (especially in the case of pre-population import)
297
298         return artifact();
299     }
300
301     /**
302      * Show the dependencies of this versioned project tab.
303      */
304     public String dependencyTree()
305     {
306         // temporarily use this as we only need the model for the tag to perform, but we should be resolving the
307         // graph here instead
308
309         // TODO: may need to note on the page that tree will be incomplete if the other artifacts are not yet stored in
310         // the content repository
311         // (especially in the case of pre-population import)
312
313         // TODO: a bit ugly, should really be mapping all these results differently now
314         this.dependencyTree = true;
315
316         return artifact();
317     }
318
319     public String projectMetadata()
320     {
321         String result = artifact();
322
323         if ( model.getFacet( GenericMetadataFacet.FACET_ID ) != null )
324         {
325             genericMetadata = model.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
326         }
327
328         if ( genericMetadata == null )
329         {
330             genericMetadata = new HashMap<String, String>();
331         }
332
333         return result;
334     }
335
336     public String addMetadataProperty()
337     {
338         RepositorySession repositorySession = repositorySessionFactory.createSession();
339         ProjectVersionMetadata projectMetadata;
340         try
341         {
342             MetadataRepository metadataRepository = repositorySession.getRepository();
343             projectMetadata = getProjectVersionMetadata( repositorySession );
344             if ( projectMetadata == null )
345             {
346                 addActionError( "Artifact not found" );
347                 return ERROR;
348             }
349
350             if ( projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ) == null )
351             {
352                 genericMetadata = new HashMap<String, String>();
353             }
354             else
355             {
356                 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
357             }
358
359             if ( propertyName == null || "".equals( propertyName.trim() ) || propertyValue == null || "".equals(
360                 propertyValue.trim() ) )
361             {
362                 model = projectMetadata;
363                 addActionError( "Property Name and Property Value are required." );
364                 return INPUT;
365             }
366
367             genericMetadata.put( propertyName, propertyValue );
368
369             try
370             {
371                 updateProjectMetadata( projectMetadata, metadataRepository );
372                 repositorySession.save();
373             }
374             catch ( MetadataRepositoryException e )
375             {
376                 log.warn( "Unable to persist modified project metadata after adding entry: " + e.getMessage(), e );
377                 addActionError(
378                     "Unable to add metadata item to underlying content storage - consult application logs." );
379                 return ERROR;
380             }
381
382             // TODO: why re-retrieve?
383             projectMetadata = getProjectVersionMetadata( repositorySession );
384         }
385         finally
386         {
387             repositorySession.close();
388         }
389
390         genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
391
392         model = projectMetadata;
393
394         propertyName = "";
395         propertyValue = "";
396
397         return SUCCESS;
398     }
399
400     public String deleteMetadataEntry()
401     {
402         RepositorySession repositorySession = repositorySessionFactory.createSession();
403         try
404         {
405             MetadataRepository metadataRepository = repositorySession.getRepository();
406             ProjectVersionMetadata projectMetadata = getProjectVersionMetadata( repositorySession );
407
408             if ( projectMetadata == null )
409             {
410                 addActionError( "Artifact not found" );
411                 return ERROR;
412             }
413
414             if ( projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ) != null )
415             {
416                 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
417
418                 if ( !StringUtils.isEmpty( deleteItem ) )
419                 {
420                     genericMetadata.remove( deleteItem );
421
422                     try
423                     {
424                         updateProjectMetadata( projectMetadata, metadataRepository );
425                         repositorySession.save();
426                     }
427                     catch ( MetadataRepositoryException e )
428                     {
429                         log.warn( "Unable to persist modified project metadata after removing entry: " + e.getMessage(),
430                                   e );
431                         addActionError(
432                             "Unable to remove metadata item to underlying content storage - consult application logs." );
433                         return ERROR;
434                     }
435
436                     // TODO: why re-retrieve?
437                     projectMetadata = getProjectVersionMetadata( repositorySession );
438
439                     genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
440
441                     model = projectMetadata;
442
443                     addActionMessage( "Property successfully deleted." );
444                 }
445
446                 deleteItem = "";
447             }
448             else
449             {
450                 addActionError( "No generic metadata facet for this artifact." );
451                 return ERROR;
452             }
453         }
454         finally
455         {
456             repositorySession.close();
457         }
458
459         return SUCCESS;
460     }
461
462     private void updateProjectMetadata( ProjectVersionMetadata projectMetadata, MetadataRepository metadataRepository )
463         throws MetadataRepositoryException
464     {
465         GenericMetadataFacet genericMetadataFacet = new GenericMetadataFacet();
466         genericMetadataFacet.fromProperties( genericMetadata );
467
468         projectMetadata.addFacet( genericMetadataFacet );
469
470         metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectMetadata );
471     }
472
473     @Override
474     public void validate()
475     {
476         if ( StringUtils.isBlank( groupId ) )
477         {
478             addActionError( "You must specify a group ID to browse" );
479         }
480
481         if ( StringUtils.isBlank( artifactId ) )
482         {
483             addActionError( "You must specify a artifact ID to browse" );
484         }
485
486         if ( StringUtils.isBlank( version ) )
487         {
488             addActionError( "You must specify a version to browse" );
489         }
490     }
491
492     public ProjectVersionMetadata getModel()
493     {
494         return model;
495     }
496
497     public String getGroupId()
498     {
499         return groupId;
500     }
501
502     public void setGroupId( String groupId )
503     {
504         this.groupId = groupId;
505     }
506
507     public String getArtifactId()
508     {
509         return artifactId;
510     }
511
512     public void setArtifactId( String artifactId )
513     {
514         this.artifactId = artifactId;
515     }
516
517     public String getVersion()
518     {
519         return version;
520     }
521
522     public void setVersion( String version )
523     {
524         this.version = version;
525     }
526
527     public List<MailingList> getMailingLists()
528     {
529         return mailingLists;
530     }
531
532     public List<Dependency> getDependencies()
533     {
534         return dependencies;
535     }
536
537     public List<ProjectVersionReference> getDependees()
538     {
539         return dependees;
540     }
541
542     public String getRepositoryId()
543     {
544         return repositoryId;
545     }
546
547     public void setRepositoryId( String repositoryId )
548     {
549         this.repositoryId = repositoryId;
550     }
551
552     public Map<String, List<ArtifactDownloadInfo>> getArtifacts()
553     {
554         return artifacts;
555     }
556
557     public Collection<String> getSnapshotVersions()
558     {
559         return artifacts.keySet();
560     }
561
562     public boolean isDependencyTree()
563     {
564         return dependencyTree;
565     }
566
567     public void setDeleteItem( String deleteItem )
568     {
569         this.deleteItem = deleteItem;
570     }
571
572     public Map<String, String> getGenericMetadata()
573     {
574         return genericMetadata;
575     }
576
577     public void setGenericMetadata( Map<String, String> genericMetadata )
578     {
579         this.genericMetadata = genericMetadata;
580     }
581
582     public String getPropertyName()
583     {
584         return propertyName;
585     }
586
587     public void setPropertyName( String propertyName )
588     {
589         this.propertyName = propertyName;
590     }
591
592     public String getPropertyValue()
593     {
594         return propertyValue;
595     }
596
597     public void setPropertyValue( String propertyValue )
598     {
599         this.propertyValue = propertyValue;
600     }
601
602     public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
603     {
604         this.repositoryFactory = repositoryFactory;
605     }
606
607     // TODO: move this into the artifact metadata itself via facets where necessary
608
609     public class ArtifactDownloadInfo
610     {
611         private String type;
612
613         private String namespace;
614
615         private String project;
616
617         private String size;
618
619         private String id;
620
621         private String repositoryId;
622
623         private String version;
624
625         private String path;
626
627         public ArtifactDownloadInfo( ArtifactMetadata artifact )
628         {
629             repositoryId = artifact.getRepositoryId();
630
631             // TODO: use metadata resolver capability instead - maybe the storage path could be stored in the metadata
632             // though keep in mind the request may not necessarily need to reflect the storage
633             ManagedRepositoryContent repo;
634             try
635             {
636                 repo = repositoryFactory.getManagedRepositoryContent( repositoryId );
637             }
638             catch ( RepositoryException e )
639             {
640                 throw new RuntimeException( e );
641             }
642
643             ArtifactReference ref = new ArtifactReference();
644             ref.setArtifactId( artifact.getProject() );
645             ref.setGroupId( artifact.getNamespace() );
646             ref.setVersion( artifact.getVersion() );
647             path = repo.toPath( ref );
648             path = path.substring( 0, path.lastIndexOf( "/" ) + 1 ) + artifact.getId();
649
650             // TODO: need to accommodate Maven 1 layout too. Non-maven repository formats will need to generate this
651             // facet (perhaps on the fly) if wanting to display the Maven 2 elements on the Archiva pages
652             String type = null;
653             MavenArtifactFacet facet = (MavenArtifactFacet) artifact.getFacet( MavenArtifactFacet.FACET_ID );
654             if ( facet != null )
655             {
656                 type = facet.getType();
657             }
658             this.type = type;
659
660             namespace = artifact.getNamespace();
661             project = artifact.getProject();
662
663             // TODO: find a reusable formatter for this
664             double s = artifact.getSize();
665             String symbol = "b";
666             if ( s > 1024 )
667             {
668                 symbol = "K";
669                 s /= 1024;
670
671                 if ( s > 1024 )
672                 {
673                     symbol = "M";
674                     s /= 1024;
675
676                     if ( s > 1024 )
677                     {
678                         symbol = "G";
679                         s /= 1024;
680                     }
681                 }
682             }
683
684             DecimalFormat df = new DecimalFormat( "#,###.##", new DecimalFormatSymbols( Locale.US) );
685             size = df.format( s ) + " " + symbol;
686             id = artifact.getId();
687             version = artifact.getVersion();
688         }
689
690         public String getNamespace()
691         {
692             return namespace;
693         }
694
695         public String getType()
696         {
697             return type;
698         }
699
700         public String getProject()
701         {
702             return project;
703         }
704
705         public String getSize()
706         {
707             return size;
708         }
709
710         public String getId()
711         {
712             return id;
713         }
714
715         public String getVersion()
716         {
717             return version;
718         }
719
720         public String getRepositoryId()
721         {
722             return repositoryId;
723         }
724
725         public String getPath()
726         {
727             return path;
728         }
729     }
730 }