1 package org.apache.maven.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.metadata.repository.MetadataRepository;
24 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
25 import org.apache.archiva.metadata.repository.stats.RepositoryStatistics;
26 import org.apache.archiva.metadata.repository.stats.RepositoryStatisticsManager;
27 import org.apache.archiva.reports.RepositoryProblemFacet;
28 import org.apache.commons.io.IOUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.apache.commons.lang.time.DateUtils;
31 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
32 import org.apache.maven.archiva.security.ArchivaRoleConstants;
33 import org.apache.maven.archiva.web.action.AbstractRepositoryBasedAction;
34 import org.codehaus.plexus.redback.rbac.Resource;
35 import org.codehaus.redback.integration.interceptor.SecureAction;
36 import org.codehaus.redback.integration.interceptor.SecureActionBundle;
37 import org.codehaus.redback.integration.interceptor.SecureActionException;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import java.io.ByteArrayInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.StringReader;
45 import java.text.ParseException;
46 import java.util.ArrayList;
47 import java.util.Calendar;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.Date;
51 import java.util.List;
53 import java.util.TreeMap;
56 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="generateReport" instantiation-strategy="per-lookup"
58 public class GenerateReportAction
59 extends AbstractRepositoryBasedAction
60 implements SecureAction, Preparable
62 public static final String ALL_REPOSITORIES = "All Repositories";
64 public static final String BLANK = "blank";
66 private static final String[] datePatterns =
67 new String[]{"MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
68 "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy"};
70 public static final String SEND_FILE = "send-file";
72 private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
77 private ArchivaConfiguration archivaConfiguration;
82 private RepositoryStatisticsManager repositoryStatisticsManager;
84 private String groupId;
86 private String repositoryId;
90 private int rowCount = 100;
92 private List<String> selectedRepositories = new ArrayList<String>();
94 private String startDate;
96 private String endDate;
100 private Collection<String> repositoryIds;
102 private Map<String, List<RepositoryProblemFacet>> repositoriesMap =
103 new TreeMap<String, List<RepositoryProblemFacet>>();
105 private List<String> availableRepositories;
107 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
109 private InputStream inputStream;
111 private boolean lastPage;
114 * @plexus.requirement
116 private MetadataRepository metadataRepository;
118 @SuppressWarnings( "unchecked" )
119 public void prepare()
121 repositoryIds = new ArrayList<String>();
122 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
123 repositoryIds.addAll( getObservableRepos() );
125 availableRepositories = new ArrayList<String>();
127 // remove selected repositories in the option for the statistics report
128 availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
129 for ( String repo : selectedRepositories )
131 if ( availableRepositories.contains( repo ) )
133 availableRepositories.remove( repo );
139 * Generate the statistics report.
141 * check whether single repo report or comparison report
142 * 1. if it is a single repository, get all the statistics for the repository on the specified date
143 * - if no date is specified, get only the latest
144 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
145 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
146 * - required params: repository, startDate, endDate
148 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
149 * - if no date is specified, use the current date endDate
150 * - required params: repositories, endDate
151 * - total pages = repositories / rows per page
153 * @return action result
155 public String generateStatistics()
159 // TODO: move to validation framework
160 addFieldError( "rowCount", "Row count must be larger than 10." );
166 if ( selectedRepositories.size() > 1 )
172 startDateInDF = getStartDateInDateFormat();
173 endDateInDF = getEndDateInDateFormat();
175 catch ( ParseException e )
177 addActionError( "Error parsing date(s)." );
181 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
183 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
188 for ( String repo : selectedRepositories )
190 List<RepositoryStatistics> stats = null;
193 stats = repositoryStatisticsManager.getStatisticsInRange( repo, startDateInDF, endDateInDF );
195 catch ( MetadataRepositoryException e )
197 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
199 if ( stats == null || stats.isEmpty() )
201 log.info( "No statistics available for repository '" + repo + "'." );
202 // TODO set repo's stats to 0
206 repositoryStatistics.add( stats.get( 0 ) );
209 else if ( selectedRepositories.size() == 1 )
211 repositoryId = selectedRepositories.get( 0 );
214 startDateInDF = getStartDateInDateFormat();
215 endDateInDF = getEndDateInDateFormat();
217 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
219 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
223 List<RepositoryStatistics> stats = null;
226 stats = repositoryStatisticsManager.getStatisticsInRange( repositoryId, startDateInDF,
229 catch ( MetadataRepositoryException e )
231 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
233 if ( stats == null || stats.isEmpty() )
235 addActionError( "No statistics available for repository. Repository might not have been scanned." );
239 int rowCount = getRowCount();
240 int extraPage = ( stats.size() % rowCount ) != 0 ? 1 : 0;
241 int totalPages = ( stats.size() / rowCount ) + extraPage;
242 numPages = totalPages;
244 int currentPage = getPage();
245 if ( currentPage > totalPages )
248 "Error encountered while generating report :: The requested page exceeds the total number of pages." );
252 int start = rowCount * ( currentPage - 1 );
253 int end = ( start + rowCount ) - 1;
255 if ( end > stats.size() )
257 end = stats.size() - 1;
260 repositoryStatistics = stats.subList( start, end + 1 );
262 catch ( ParseException pe )
264 addActionError( pe.getMessage() );
270 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
274 if ( repositoryStatistics.isEmpty() )
283 * Export report to CSV.
285 * @return action result
287 public String downloadStatisticsReport()
292 selectedRepositories = parseSelectedRepositories();
293 List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
296 if ( selectedRepositories.size() > 1 )
300 startDateInDF = getStartDateInDateFormat();
301 endDateInDF = getEndDateInDateFormat();
303 catch ( ParseException e )
305 addActionError( "Error parsing date(s)." );
309 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
311 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
315 input = new StringBuffer(
316 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,Archetypes," +
320 for ( String repo : selectedRepositories )
322 List<RepositoryStatistics> stats = null;
325 stats = repositoryStatisticsManager.getStatisticsInRange( repo, startDateInDF, endDateInDF );
327 catch ( MetadataRepositoryException e )
329 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
331 if ( stats == null || stats.isEmpty() )
333 log.info( "No statistics available for repository '" + repo + "'." );
334 // TODO set repo's stats to 0
338 // only the first one
339 RepositoryStatistics repositoryStats = stats.get( 0 );
340 repositoryStatistics.add( repositoryStats );
342 input.append( repo ).append( "," );
343 input.append( repositoryStats.getTotalFileCount() ).append( "," );
344 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
345 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
346 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
347 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
348 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
349 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
350 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
351 input.append( repositoryStats.getTotalCountForType( "war" ) );
352 input.append( "\n" );
355 else if ( selectedRepositories.size() == 1 )
357 repositoryId = selectedRepositories.get( 0 );
360 startDateInDF = getStartDateInDateFormat();
361 endDateInDF = getEndDateInDateFormat();
363 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
365 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
369 List<RepositoryStatistics> stats = null;
372 stats = repositoryStatisticsManager.getStatisticsInRange( repositoryId, startDateInDF,
375 catch ( MetadataRepositoryException e )
377 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
379 if ( stats == null || stats.isEmpty() )
381 addActionError( "No statistics available for repository. Repository might not have been scanned." );
385 input = new StringBuffer(
386 "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins," +
387 "Archetypes,Jars,Wars\n" );
389 for ( RepositoryStatistics repositoryStats : stats )
391 input.append( repositoryStats.getScanStartTime() ).append( "," );
392 input.append( repositoryStats.getTotalFileCount() ).append( "," );
393 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
394 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
395 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
396 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
397 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
398 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
399 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
400 input.append( repositoryStats.getTotalCountForType( "war" ) );
401 input.append( "\n" );
404 repositoryStatistics = stats;
406 catch ( ParseException pe )
408 addActionError( pe.getMessage() );
414 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
418 if ( repositoryStatistics.isEmpty() )
423 // write output stream depending on single or comparison report
424 StringReader reader = new StringReader( input.toString() );
428 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
430 catch ( IOException i )
432 addActionError( "Error occurred while generating CSV file." );
439 // hack for parsing the struts list passed as param in <s:url ../>
441 private List<String> parseSelectedRepositories()
443 List<String> parsedSelectedRepos = new ArrayList<String>();
445 for ( String repo : selectedRepositories )
447 String[] tokens = StringUtils.split( repo, ',' );
448 if ( tokens.length > 1 )
450 for ( String token : tokens )
452 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( token, '[' ), ']' ).trim() );
457 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
460 return parsedSelectedRepos;
463 private Date getStartDateInDateFormat()
464 throws ParseException
467 if ( startDate == null || "".equals( startDate ) )
469 startDateInDF = null;
473 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
475 return startDateInDF;
478 private Date getEndDateInDateFormat()
479 throws ParseException
482 if ( endDate == null || "".equals( endDate ) )
488 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
490 // add a day, since we don't inclue time and want the date to be inclusive
491 Calendar cal = Calendar.getInstance();
492 cal.setTime( endDateInDF );
493 cal.add( Calendar.DAY_OF_MONTH, 1 );
494 endDateInDF = cal.getTime();
500 public String execute()
503 if ( repositoryId == null )
505 addFieldError( "repositoryId", "You must provide a repository id." );
511 addFieldError( "rowCount", "Row count must be larger than 10." );
515 List<String> observableRepos = getObservableRepos();
516 Collection<String> repoIds;
517 if ( StringUtils.isEmpty( repositoryId ) || ALL_REPOSITORIES.equals( repositoryId ) )
519 repoIds = observableRepos;
521 else if ( observableRepos.contains( repositoryId ) )
523 repoIds = Collections.singletonList( repositoryId );
527 repoIds = Collections.emptyList();
530 List<RepositoryProblemFacet> problemArtifacts = new ArrayList<RepositoryProblemFacet>();
531 for ( String repoId : repoIds )
533 // TODO: improve performance by navigating into a group subtree. Currently group is property, not part of name of item
534 for ( String name : metadataRepository.getMetadataFacets( repoId, RepositoryProblemFacet.FACET_ID ) )
536 RepositoryProblemFacet metadataFacet = (RepositoryProblemFacet) metadataRepository.getMetadataFacet(
537 repoId, RepositoryProblemFacet.FACET_ID, name );
539 if ( StringUtils.isEmpty( groupId ) || groupId.equals( metadataFacet.getNamespace() ) )
541 problemArtifacts.add( metadataFacet );
546 // TODO: getting range only after reading is not efficient for a large number of artifacts
547 int lowerBound = ( page - 1 ) * rowCount;
548 int upperBound = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
549 if ( upperBound <= problemArtifacts.size() )
551 problemArtifacts = problemArtifacts.subList( lowerBound, upperBound );
555 problemArtifacts = problemArtifacts.subList( lowerBound, problemArtifacts.size() );
558 for ( RepositoryProblemFacet problem : problemArtifacts )
560 List<RepositoryProblemFacet> problemsList;
561 if ( repositoriesMap.containsKey( problem.getRepositoryId() ) )
563 problemsList = repositoriesMap.get( problem.getRepositoryId() );
567 problemsList = new ArrayList<RepositoryProblemFacet>();
568 repositoriesMap.put( problem.getRepositoryId(), problemsList );
571 problemsList.add( problem );
574 // TODO: handling should be improved
575 if ( problemArtifacts.size() <= rowCount )
580 if ( problemArtifacts.isEmpty() && page == 1 )
590 public SecureActionBundle getSecureActionBundle()
591 throws SecureActionException
593 SecureActionBundle bundle = new SecureActionBundle();
595 bundle.setRequiresAuthentication( true );
596 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
601 public Collection<String> getRepositoryIds()
603 return repositoryIds;
606 public String getGroupId()
611 public void setGroupId( String groupId )
613 this.groupId = groupId;
616 public String getRepositoryId()
621 public void setRepositoryId( String repositoryId )
623 this.repositoryId = repositoryId;
631 public void setPage( int page )
636 public int getRowCount()
641 public void setRowCount( int rowCount )
643 this.rowCount = rowCount;
646 public void setRepositoriesMap( Map<String, List<RepositoryProblemFacet>> repositoriesMap )
648 this.repositoriesMap = repositoriesMap;
651 public Map<String, List<RepositoryProblemFacet>> getRepositoriesMap()
653 return repositoriesMap;
656 public List<String> getSelectedRepositories()
658 return selectedRepositories;
661 public void setSelectedRepositories( List<String> selectedRepositories )
663 this.selectedRepositories = selectedRepositories;
666 public List<String> getAvailableRepositories()
668 return availableRepositories;
671 public void setAvailableRepositories( List<String> availableRepositories )
673 this.availableRepositories = availableRepositories;
676 public String getStartDate()
681 public void setStartDate( String startDate )
683 this.startDate = startDate;
686 public String getEndDate()
691 public void setEndDate( String endDate )
693 this.endDate = endDate;
696 public List<RepositoryStatistics> getRepositoryStatistics()
698 return repositoryStatistics;
701 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
703 this.repositoryStatistics = repositoryStatistics;
706 public boolean isLastPage()
711 public void setLastPage( boolean lastPage )
713 this.lastPage = lastPage;
716 public InputStream getInputStream()
721 public int getNumPages()
726 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
728 this.repositoryStatisticsManager = repositoryStatisticsManager;
731 public void setMetadataRepository( MetadataRepository metadataRepository )
733 this.metadataRepository = metadataRepository;