1 package org.apache.archiva.web.action.reports;
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.Preparable;
23 import org.apache.archiva.admin.model.RepositoryAdminException;
24 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
25 import org.apache.archiva.metadata.repository.MetadataRepository;
26 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
27 import org.apache.archiva.metadata.repository.RepositorySession;
28 import org.apache.archiva.metadata.repository.stats.RepositoryStatistics;
29 import org.apache.archiva.metadata.repository.stats.RepositoryStatisticsManager;
30 import org.apache.archiva.reports.RepositoryProblemFacet;
31 import org.apache.archiva.security.common.ArchivaRoleConstants;
32 import org.apache.commons.io.IOUtils;
33 import org.apache.commons.lang.StringUtils;
34 import org.apache.commons.lang.time.DateUtils;
35 import org.apache.archiva.web.action.AbstractRepositoryBasedAction;
36 import org.apache.archiva.redback.rbac.Resource;
37 import org.apache.archiva.redback.integration.interceptor.SecureAction;
38 import org.apache.archiva.redback.integration.interceptor.SecureActionBundle;
39 import org.apache.archiva.redback.integration.interceptor.SecureActionException;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.springframework.context.annotation.Scope;
43 import org.springframework.stereotype.Controller;
45 import javax.inject.Inject;
46 import java.io.ByteArrayInputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.StringReader;
50 import java.text.ParseException;
51 import java.util.ArrayList;
52 import java.util.Calendar;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.Date;
56 import java.util.List;
58 import java.util.TreeMap;
63 @Controller( "generateReport" )
65 public class GenerateReportAction
66 extends AbstractRepositoryBasedAction
67 implements SecureAction, Preparable
69 public static final String ALL_REPOSITORIES = "All Repositories";
71 public static final String BLANK = "blank";
73 private static final String[] datePatterns =
74 new String[]{ "MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
75 "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy" };
77 public static final String SEND_FILE = "send-file";
79 private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
82 private ManagedRepositoryAdmin managedRepositoryAdmin;
85 private RepositoryStatisticsManager repositoryStatisticsManager;
87 private String groupId;
89 private String repositoryId;
93 private int rowCount = 100;
95 private List<String> selectedRepositories = new ArrayList<String>();
97 private String startDate;
99 private String endDate;
101 private int numPages;
103 private List<String> repositoryIds;
105 private Map<String, List<RepositoryProblemFacet>> repositoriesMap =
106 new TreeMap<String, List<RepositoryProblemFacet>>();
108 private List<String> availableRepositories;
110 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
112 private InputStream inputStream;
114 private boolean lastPage;
116 @SuppressWarnings( "unchecked" )
117 public void prepare()
118 throws RepositoryAdminException
120 repositoryIds = new ArrayList<String>();
121 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
122 repositoryIds.addAll( getObservableRepos() );
124 availableRepositories = new ArrayList<String>();
126 // remove selected repositories in the option for the statistics report
127 availableRepositories.addAll( managedRepositoryAdmin.getManagedRepositoriesAsMap().keySet() );
128 for ( String repo : selectedRepositories )
130 if ( availableRepositories.contains( repo ) )
132 availableRepositories.remove( repo );
138 * Generate the statistics report.
140 * check whether single repo report or comparison report
141 * 1. if it is a single repository, get all the statistics for the repository on the specified date
142 * - if no date is specified, get only the latest
143 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
144 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
145 * - required params: repository, startDate, endDate
147 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
148 * - if no date is specified, use the current date endDate
149 * - required params: repositories, endDate
150 * - total pages = repositories / rows per page
152 * @return action result
154 public String generateStatistics()
158 // TODO: move to validation framework
159 addFieldError( "rowCount", "Row count must be larger than 10." );
165 RepositorySession repositorySession = repositorySessionFactory.createSession();
168 MetadataRepository metadataRepository = repositorySession.getRepository();
169 if ( selectedRepositories.size() > 1 )
175 startDateInDF = getStartDateInDateFormat();
176 endDateInDF = getEndDateInDateFormat();
178 catch ( ParseException e )
180 addActionError( "Error parsing date(s)." );
184 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
186 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
191 for ( String repo : selectedRepositories )
193 List<RepositoryStatistics> stats = null;
197 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
200 catch ( MetadataRepositoryException e )
202 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
204 if ( stats == null || stats.isEmpty() )
206 log.info( "No statistics available for repository '" + repo + "'." );
207 // TODO set repo's stats to 0
211 repositoryStatistics.add( stats.get( 0 ) );
214 else if ( selectedRepositories.size() == 1 )
216 repositoryId = selectedRepositories.get( 0 );
219 startDateInDF = getStartDateInDateFormat();
220 endDateInDF = getEndDateInDateFormat();
222 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
224 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
228 List<RepositoryStatistics> stats = null;
231 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
232 startDateInDF, endDateInDF );
234 catch ( MetadataRepositoryException e )
236 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
238 if ( stats == null || stats.isEmpty() )
241 "No statistics available for repository. Repository might not have been scanned." );
245 int rowCount = getRowCount();
246 int extraPage = ( stats.size() % rowCount ) != 0 ? 1 : 0;
247 int totalPages = ( stats.size() / rowCount ) + extraPage;
248 numPages = totalPages;
250 int currentPage = getPage();
251 if ( currentPage > totalPages )
254 "Error encountered while generating report :: The requested page exceeds the total number of pages." );
258 int start = rowCount * ( currentPage - 1 );
259 int end = ( start + rowCount ) - 1;
261 if ( end >= stats.size() )
263 end = stats.size() - 1;
266 repositoryStatistics = stats.subList( start, end + 1 );
268 catch ( ParseException pe )
270 addActionError( pe.getMessage() );
276 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
282 repositorySession.close();
285 if ( repositoryStatistics.isEmpty() )
294 * Export report to CSV.
296 * @return action result
298 public String downloadStatisticsReport()
303 selectedRepositories = parseSelectedRepositories();
304 List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
307 RepositorySession repositorySession = repositorySessionFactory.createSession();
310 MetadataRepository metadataRepository = repositorySession.getRepository();
311 if ( selectedRepositories.size() > 1 )
315 startDateInDF = getStartDateInDateFormat();
316 endDateInDF = getEndDateInDateFormat();
318 catch ( ParseException e )
320 addActionError( "Error parsing date(s)." );
324 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
326 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
330 input = new StringBuilder(
331 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,Archetypes,"
335 for ( String repo : selectedRepositories )
337 List<RepositoryStatistics> stats = null;
341 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
344 catch ( MetadataRepositoryException e )
346 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
348 if ( stats == null || stats.isEmpty() )
350 log.info( "No statistics available for repository '" + repo + "'." );
351 // TODO set repo's stats to 0
355 // only the first one
356 RepositoryStatistics repositoryStats = stats.get( 0 );
357 repositoryStatistics.add( repositoryStats );
359 input.append( repo ).append( "," );
360 input.append( repositoryStats.getTotalFileCount() ).append( "," );
361 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
362 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
363 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
364 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
365 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
366 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
367 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
368 input.append( repositoryStats.getTotalCountForType( "war" ) );
369 input.append( "\n" );
372 else if ( selectedRepositories.size() == 1 )
374 repositoryId = selectedRepositories.get( 0 );
377 startDateInDF = getStartDateInDateFormat();
378 endDateInDF = getEndDateInDateFormat();
380 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
382 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
386 List<RepositoryStatistics> stats = null;
389 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
390 startDateInDF, endDateInDF );
392 catch ( MetadataRepositoryException e )
394 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
396 if ( stats == null || stats.isEmpty() )
399 "No statistics available for repository. Repository might not have been scanned." );
403 input = new StringBuilder(
404 "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,"
405 + "Archetypes,Jars,Wars\n" );
407 for ( RepositoryStatistics repositoryStats : stats )
409 input.append( repositoryStats.getScanStartTime() ).append( "," );
410 input.append( repositoryStats.getTotalFileCount() ).append( "," );
411 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
412 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
413 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
414 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
415 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
416 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
417 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
418 input.append( repositoryStats.getTotalCountForType( "war" ) );
419 input.append( "\n" );
422 repositoryStatistics = stats;
424 catch ( ParseException pe )
426 addActionError( pe.getMessage() );
432 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
438 repositorySession.close();
441 if ( repositoryStatistics.isEmpty() )
446 // write output stream depending on single or comparison report
447 StringReader reader = new StringReader( input.toString() );
451 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
453 catch ( IOException i )
455 addActionError( "Error occurred while generating CSV file." );
462 // hack for parsing the struts list passed as param in <s:url ../>
464 private List<String> parseSelectedRepositories()
466 List<String> parsedSelectedRepos = new ArrayList<String>();
468 for ( String repo : selectedRepositories )
470 String[] tokens = StringUtils.split( repo, ',' );
471 if ( tokens.length > 1 )
473 for ( String token : tokens )
475 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( token, '[' ), ']' ).trim() );
480 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
483 return parsedSelectedRepos;
486 private Date getStartDateInDateFormat()
487 throws ParseException
490 if ( startDate == null || "".equals( startDate ) )
492 startDateInDF = null;
496 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
498 return startDateInDF;
501 private Date getEndDateInDateFormat()
502 throws ParseException
505 if ( endDate == null || "".equals( endDate ) )
511 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
513 // add a day, since we don't inclue time and want the date to be inclusive
514 Calendar cal = Calendar.getInstance();
515 cal.setTime( endDateInDF );
516 cal.add( Calendar.DAY_OF_MONTH, 1 );
517 endDateInDF = cal.getTime();
523 public String execute()
526 if ( repositoryId == null )
528 addFieldError( "repositoryId", "You must provide a repository id." );
534 addFieldError( "rowCount", "Row count must be larger than 10." );
538 List<String> observableRepos = getObservableRepos();
539 Collection<String> repoIds;
540 if ( StringUtils.isEmpty( repositoryId ) || ALL_REPOSITORIES.equals( repositoryId ) )
542 repoIds = observableRepos;
544 else if ( observableRepos.contains( repositoryId ) )
546 repoIds = Collections.singletonList( repositoryId );
550 repoIds = Collections.emptyList();
553 List<RepositoryProblemFacet> problemArtifacts = new ArrayList<RepositoryProblemFacet>();
554 RepositorySession repositorySession = repositorySessionFactory.createSession();
557 MetadataRepository metadataRepository = repositorySession.getRepository();
558 for ( String repoId : repoIds )
560 // TODO: improve performance by navigating into a group subtree. Currently group is property, not part of name of item
561 for ( String name : metadataRepository.getMetadataFacets( repoId, RepositoryProblemFacet.FACET_ID ) )
563 RepositoryProblemFacet metadataFacet =
564 (RepositoryProblemFacet) metadataRepository.getMetadataFacet( repoId,
565 RepositoryProblemFacet.FACET_ID,
568 if ( StringUtils.isEmpty( groupId ) || groupId.equals( metadataFacet.getNamespace() ) )
570 problemArtifacts.add( metadataFacet );
577 repositorySession.close();
580 // TODO: getting range only after reading is not efficient for a large number of artifacts
581 int lowerBound = ( page - 1 ) * rowCount;
582 int upperBound = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
583 if ( upperBound <= problemArtifacts.size() )
585 problemArtifacts = problemArtifacts.subList( lowerBound, upperBound );
589 problemArtifacts = problemArtifacts.subList( lowerBound, problemArtifacts.size() );
592 for ( RepositoryProblemFacet problem : problemArtifacts )
594 List<RepositoryProblemFacet> problemsList;
595 if ( repositoriesMap.containsKey( problem.getRepositoryId() ) )
597 problemsList = repositoriesMap.get( problem.getRepositoryId() );
601 problemsList = new ArrayList<RepositoryProblemFacet>();
602 repositoriesMap.put( problem.getRepositoryId(), problemsList );
605 problemsList.add( problem );
608 // TODO: handling should be improved
609 if ( problemArtifacts.size() <= rowCount )
614 if ( problemArtifacts.isEmpty() && page == 1 )
624 public SecureActionBundle getSecureActionBundle()
625 throws SecureActionException
627 SecureActionBundle bundle = new SecureActionBundle();
629 bundle.setRequiresAuthentication( true );
630 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
635 public List<String> getRepositoryIds()
637 return repositoryIds;
640 public String getGroupId()
645 public void setGroupId( String groupId )
647 this.groupId = groupId;
650 public String getRepositoryId()
655 public void setRepositoryId( String repositoryId )
657 this.repositoryId = repositoryId;
665 public void setPage( int page )
670 public int getRowCount()
675 public void setRowCount( int rowCount )
677 this.rowCount = rowCount;
680 public void setRepositoriesMap( Map<String, List<RepositoryProblemFacet>> repositoriesMap )
682 this.repositoriesMap = repositoriesMap;
685 public Map<String, List<RepositoryProblemFacet>> getRepositoriesMap()
687 return repositoriesMap;
690 public List<String> getSelectedRepositories()
692 return selectedRepositories;
695 public void setSelectedRepositories( List<String> selectedRepositories )
697 this.selectedRepositories = selectedRepositories;
700 public List<String> getAvailableRepositories()
702 return availableRepositories;
705 public void setAvailableRepositories( List<String> availableRepositories )
707 this.availableRepositories = availableRepositories;
710 public String getStartDate()
715 public void setStartDate( String startDate )
717 this.startDate = startDate;
720 public String getEndDate()
725 public void setEndDate( String endDate )
727 this.endDate = endDate;
730 public List<RepositoryStatistics> getRepositoryStatistics()
732 return repositoryStatistics;
735 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
737 this.repositoryStatistics = repositoryStatistics;
740 public boolean isLastPage()
745 public void setLastPage( boolean lastPage )
747 this.lastPage = lastPage;
750 public InputStream getInputStream()
755 public int getNumPages()
760 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
762 this.repositoryStatisticsManager = repositoryStatisticsManager;
765 public ManagedRepositoryAdmin getManagedRepositoryAdmin()
767 return managedRepositoryAdmin;
770 public void setManagedRepositoryAdmin( ManagedRepositoryAdmin managedRepositoryAdmin )
772 this.managedRepositoryAdmin = managedRepositoryAdmin;