1 package org.apache.maven.archiva.web.action;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
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.MetadataFacet;
28 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
29 import org.apache.archiva.metadata.model.ProjectVersionReference;
30 import org.apache.archiva.metadata.repository.MetadataRepository;
31 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
32 import org.apache.archiva.metadata.repository.MetadataResolutionException;
33 import org.apache.archiva.metadata.repository.MetadataResolver;
34 import org.apache.archiva.metadata.repository.RepositorySession;
35 import org.apache.archiva.metadata.repository.storage.maven2.MavenArtifactFacet;
36 import org.apache.archiva.reports.RepositoryProblemFacet;
37 import org.apache.commons.lang.StringUtils;
38 import org.apache.maven.archiva.model.ArtifactReference;
39 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
40 import org.apache.maven.archiva.repository.RepositoryContentFactory;
41 import org.apache.maven.archiva.repository.RepositoryException;
42 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
43 import org.springframework.context.annotation.Scope;
44 import org.springframework.stereotype.Controller;
46 import java.text.DecimalFormat;
47 import java.text.DecimalFormatSymbols;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.HashMap;
53 import java.util.LinkedHashMap;
54 import java.util.List;
55 import java.util.Locale;
57 import javax.inject.Inject;
60 * Browse the repository.
62 * TODO change name to ShowVersionedAction to conform to terminology.
64 * plexus.component role="com.opensymphony.xwork2.Action" role-hint="showArtifactAction"
65 * instantiation-strategy="per-lookup"
67 @SuppressWarnings( "serial" )
68 @Controller( "showArtifactAction" )
70 public class ShowArtifactAction
71 extends AbstractRepositoryBasedAction
72 implements Validateable
74 /* .\ Not Exposed \._____________________________________________ */
80 private RepositoryContentFactory repositoryFactory;
82 /* .\ Exposed Output Objects \.__________________________________ */
84 private String groupId;
86 private String artifactId;
88 private String version;
90 private String repositoryId;
93 * The model of this versioned project.
95 private ProjectVersionMetadata model;
98 * The list of artifacts that depend on this versioned project.
100 private List<ProjectVersionReference> dependees;
102 private List<MailingList> mailingLists;
104 private List<Dependency> dependencies;
106 private Map<String, List<ArtifactDownloadInfo>> artifacts;
108 private boolean dependencyTree = false;
110 private String deleteItem;
112 private Map<String, String> genericMetadata;
114 private String propertyName;
116 private String propertyValue;
119 * Show the versioned project information tab. TODO: Change name to 'project' - we are showing project versions
120 * here, not specific artifact information (though that is rendered in the download box).
122 public String artifact()
124 RepositorySession repositorySession = repositorySessionFactory.createSession();
127 return handleArtifact( repositorySession );
131 repositorySession.close();
135 private String handleArtifact( RepositorySession session )
137 // In the future, this should be replaced by the repository grouping mechanism, so that we are only making
138 // simple resource requests here and letting the resolver take care of it
139 ProjectVersionMetadata versionMetadata = getProjectVersionMetadata( session );
141 if ( versionMetadata == null )
143 addActionError( "Artifact not found" );
147 if ( versionMetadata.isIncomplete() )
149 addIncompleteModelWarning( "Artifact metadata is incomplete." );
152 model = versionMetadata;
157 private ProjectVersionMetadata getProjectVersionMetadata( RepositorySession session )
159 ProjectVersionMetadata versionMetadata = null;
160 artifacts = new LinkedHashMap<String, List<ArtifactDownloadInfo>>();
162 List<String> repos = getObservableRepos();
164 MetadataResolver metadataResolver = session.getResolver();
165 for ( String repoId : repos )
167 if ( versionMetadata == null )
169 // we don't want the implementation being that intelligent - so another resolver to do the
170 // "just-in-time" nature of picking up the metadata (if appropriate for the repository type) is used
173 versionMetadata = metadataResolver.resolveProjectVersion( session, repoId, groupId, artifactId,
175 if ( versionMetadata != null )
177 MetadataFacet repoProbFacet;
178 if ( (repoProbFacet = versionMetadata.getFacet( RepositoryProblemFacet.FACET_ID ) ) != null )
180 addIncompleteModelWarning( "Artifact metadata is incomplete: " + ( ( RepositoryProblemFacet) repoProbFacet ).getProblem() );
181 //set metadata to complete so that no additional 'Artifact metadata is incomplete' warning is logged
182 versionMetadata.setIncomplete( false );
187 catch ( MetadataResolutionException e )
189 addIncompleteModelWarning( "Error resolving artifact metadata: " + e.getMessage() );
191 // TODO: need a consistent way to construct this - same in ArchivaMetadataCreationConsumer
192 versionMetadata = new ProjectVersionMetadata();
193 versionMetadata.setId( version );
195 if ( versionMetadata != null )
197 repositoryId = repoId;
199 List<ArtifactMetadata> artifacts;
202 artifacts = new ArrayList<ArtifactMetadata>( metadataResolver.resolveArtifacts( session, repoId,
207 catch ( MetadataResolutionException e )
209 addIncompleteModelWarning( "Error resolving artifact metadata: " + e.getMessage() );
210 artifacts = Collections.emptyList();
212 Collections.sort( artifacts, new Comparator<ArtifactMetadata>()
214 public int compare( ArtifactMetadata o1, ArtifactMetadata o2 )
216 // sort by version (reverse), then ID
217 // TODO: move version sorting into repository handling (maven2 specific), and perhaps add a
218 // way to get latest instead
219 int result = new DefaultArtifactVersion( o2.getVersion() ).compareTo(
220 new DefaultArtifactVersion( o1.getVersion() ) );
221 return result != 0 ? result : o1.getId().compareTo( o2.getId() );
225 for ( ArtifactMetadata artifact : artifacts )
227 List<ArtifactDownloadInfo> l = this.artifacts.get( artifact.getVersion() );
230 l = new ArrayList<ArtifactDownloadInfo>();
231 this.artifacts.put( artifact.getVersion(), l );
233 l.add( new ArtifactDownloadInfo( artifact ) );
239 return versionMetadata;
242 private void addIncompleteModelWarning( String warningMessage )
244 addActionError( warningMessage );
245 //"The model may be incomplete due to a previous error in resolving information. Refer to the repository problem reports for more information." );
249 * Show the artifact information tab.
251 public String dependencies()
253 String result = artifact();
255 this.dependencies = model.getDependencies();
261 * Show the mailing lists information tab.
263 public String mailingLists()
265 String result = artifact();
267 this.mailingLists = model.getMailingLists();
273 * Show the reports tab.
275 public String reports()
277 // TODO: hook up reports on project
283 * Show the dependees (other artifacts that depend on this project) tab.
285 public String dependees()
286 throws MetadataResolutionException
288 List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
289 // TODO: what if we get duplicates across repositories?
290 RepositorySession repositorySession = repositorySessionFactory.createSession();
293 MetadataResolver metadataResolver = repositorySession.getResolver();
294 for ( String repoId : getObservableRepos() )
296 // TODO: what about if we want to see this irrespective of version?
297 references.addAll( metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId,
298 artifactId, version ) );
303 repositorySession.close();
306 this.dependees = references;
308 // TODO: may need to note on the page that references will be incomplete if the other artifacts are not yet
309 // stored in the content repository
310 // (especially in the case of pre-population import)
316 * Show the dependencies of this versioned project tab.
318 public String dependencyTree()
320 // temporarily use this as we only need the model for the tag to perform, but we should be resolving the
321 // graph here instead
323 // TODO: may need to note on the page that tree will be incomplete if the other artifacts are not yet stored in
324 // the content repository
325 // (especially in the case of pre-population import)
327 // TODO: a bit ugly, should really be mapping all these results differently now
328 this.dependencyTree = true;
333 public String projectMetadata()
335 String result = artifact();
337 if ( model.getFacet( GenericMetadataFacet.FACET_ID ) != null )
339 genericMetadata = model.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
342 if ( genericMetadata == null )
344 genericMetadata = new HashMap<String, String>();
350 public String addMetadataProperty()
352 RepositorySession repositorySession = repositorySessionFactory.createSession();
353 ProjectVersionMetadata projectMetadata;
356 MetadataRepository metadataRepository = repositorySession.getRepository();
357 projectMetadata = getProjectVersionMetadata( repositorySession );
358 if ( projectMetadata == null )
360 addActionError( "Artifact not found" );
364 if ( projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ) == null )
366 genericMetadata = new HashMap<String, String>();
370 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
373 if ( propertyName == null || "".equals( propertyName.trim() ) || propertyValue == null || "".equals(
374 propertyValue.trim() ) )
376 model = projectMetadata;
377 addActionError( "Property Name and Property Value are required." );
381 genericMetadata.put( propertyName, propertyValue );
385 updateProjectMetadata( projectMetadata, metadataRepository );
386 repositorySession.save();
388 catch ( MetadataRepositoryException e )
390 log.warn( "Unable to persist modified project metadata after adding entry: " + e.getMessage(), e );
392 "Unable to add metadata item to underlying content storage - consult application logs." );
396 // TODO: why re-retrieve?
397 projectMetadata = getProjectVersionMetadata( repositorySession );
401 repositorySession.close();
404 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
406 model = projectMetadata;
414 public String deleteMetadataEntry()
416 RepositorySession repositorySession = repositorySessionFactory.createSession();
419 MetadataRepository metadataRepository = repositorySession.getRepository();
420 ProjectVersionMetadata projectMetadata = getProjectVersionMetadata( repositorySession );
422 if ( projectMetadata == null )
424 addActionError( "Artifact not found" );
428 if ( projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ) != null )
430 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
432 if ( !StringUtils.isEmpty( deleteItem ) )
434 genericMetadata.remove( deleteItem );
438 updateProjectMetadata( projectMetadata, metadataRepository );
439 repositorySession.save();
441 catch ( MetadataRepositoryException e )
443 log.warn( "Unable to persist modified project metadata after removing entry: " + e.getMessage(),
446 "Unable to remove metadata item to underlying content storage - consult application logs." );
450 // TODO: why re-retrieve?
451 projectMetadata = getProjectVersionMetadata( repositorySession );
453 genericMetadata = projectMetadata.getFacet( GenericMetadataFacet.FACET_ID ).toProperties();
455 model = projectMetadata;
457 addActionMessage( "Property successfully deleted." );
464 addActionError( "No generic metadata facet for this artifact." );
470 repositorySession.close();
476 private void updateProjectMetadata( ProjectVersionMetadata projectMetadata, MetadataRepository metadataRepository )
477 throws MetadataRepositoryException
479 GenericMetadataFacet genericMetadataFacet = new GenericMetadataFacet();
480 genericMetadataFacet.fromProperties( genericMetadata );
482 projectMetadata.addFacet( genericMetadataFacet );
484 metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectMetadata );
488 public void validate()
490 if ( StringUtils.isBlank( groupId ) )
492 addActionError( "You must specify a group ID to browse" );
495 if ( StringUtils.isBlank( artifactId ) )
497 addActionError( "You must specify a artifact ID to browse" );
500 if ( StringUtils.isBlank( version ) )
502 addActionError( "You must specify a version to browse" );
506 public ProjectVersionMetadata getModel()
511 public String getGroupId()
516 public void setGroupId( String groupId )
518 this.groupId = groupId;
521 public String getArtifactId()
526 public void setArtifactId( String artifactId )
528 this.artifactId = artifactId;
531 public String getVersion()
536 public void setVersion( String version )
538 this.version = version;
541 public List<MailingList> getMailingLists()
546 public List<Dependency> getDependencies()
551 public List<ProjectVersionReference> getDependees()
556 public String getRepositoryId()
561 public void setRepositoryId( String repositoryId )
563 this.repositoryId = repositoryId;
566 public Map<String, List<ArtifactDownloadInfo>> getArtifacts()
571 public Collection<String> getSnapshotVersions()
573 return artifacts.keySet();
576 public boolean isDependencyTree()
578 return dependencyTree;
581 public void setDeleteItem( String deleteItem )
583 this.deleteItem = deleteItem;
586 public Map<String, String> getGenericMetadata()
588 return genericMetadata;
591 public void setGenericMetadata( Map<String, String> genericMetadata )
593 this.genericMetadata = genericMetadata;
596 public String getPropertyName()
601 public void setPropertyName( String propertyName )
603 this.propertyName = propertyName;
606 public String getPropertyValue()
608 return propertyValue;
611 public void setPropertyValue( String propertyValue )
613 this.propertyValue = propertyValue;
616 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
618 this.repositoryFactory = repositoryFactory;
621 // TODO: move this into the artifact metadata itself via facets where necessary
623 public class ArtifactDownloadInfo
627 private String namespace;
629 private String project;
635 private String repositoryId;
637 private String version;
641 public ArtifactDownloadInfo( ArtifactMetadata artifact )
643 repositoryId = artifact.getRepositoryId();
645 // TODO: use metadata resolver capability instead - maybe the storage path could be stored in the metadata
646 // though keep in mind the request may not necessarily need to reflect the storage
647 ManagedRepositoryContent repo;
650 repo = repositoryFactory.getManagedRepositoryContent( repositoryId );
652 catch ( RepositoryException e )
654 throw new RuntimeException( e );
657 ArtifactReference ref = new ArtifactReference();
658 ref.setArtifactId( artifact.getProject() );
659 ref.setGroupId( artifact.getNamespace() );
660 ref.setVersion( artifact.getVersion() );
661 path = repo.toPath( ref );
662 path = path.substring( 0, path.lastIndexOf( "/" ) + 1 ) + artifact.getId();
664 // TODO: need to accommodate Maven 1 layout too. Non-maven repository formats will need to generate this
665 // facet (perhaps on the fly) if wanting to display the Maven 2 elements on the Archiva pages
667 MavenArtifactFacet facet = (MavenArtifactFacet) artifact.getFacet( MavenArtifactFacet.FACET_ID );
670 type = facet.getType();
674 namespace = artifact.getNamespace();
675 project = artifact.getProject();
677 // TODO: find a reusable formatter for this
678 double s = artifact.getSize();
698 DecimalFormat df = new DecimalFormat( "#,###.##", new DecimalFormatSymbols( Locale.US) );
699 size = df.format( s ) + " " + symbol;
700 id = artifact.getId();
701 version = artifact.getVersion();
704 public String getNamespace()
709 public String getType()
714 public String getProject()
719 public String getSize()
724 public String getId()
729 public String getVersion()
734 public String getRepositoryId()
739 public String getPath()