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.RepositorySession;
26 import org.apache.archiva.metadata.repository.stats.RepositoryStatistics;
27 import org.apache.archiva.metadata.repository.stats.RepositoryStatisticsManager;
28 import org.apache.archiva.reports.RepositoryProblemFacet;
29 import org.apache.commons.io.IOUtils;
30 import org.apache.commons.lang.StringUtils;
31 import org.apache.commons.lang.time.DateUtils;
32 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
33 import org.apache.maven.archiva.security.ArchivaRoleConstants;
34 import org.apache.maven.archiva.web.action.AbstractRepositoryBasedAction;
35 import org.codehaus.plexus.redback.rbac.Resource;
36 import org.codehaus.redback.integration.interceptor.SecureAction;
37 import org.codehaus.redback.integration.interceptor.SecureActionBundle;
38 import org.codehaus.redback.integration.interceptor.SecureActionException;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.springframework.context.annotation.Scope;
42 import org.springframework.stereotype.Controller;
44 import javax.inject.Inject;
45 import java.io.ByteArrayInputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.StringReader;
49 import java.text.ParseException;
50 import java.util.ArrayList;
51 import java.util.Calendar;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.Date;
55 import java.util.List;
57 import java.util.TreeMap;
60 * plexus.component role="com.opensymphony.xwork2.Action" role-hint="generateReport" instantiation-strategy="per-lookup"
62 @Controller( "generateReport" )
64 public class GenerateReportAction
65 extends AbstractRepositoryBasedAction
66 implements SecureAction, Preparable
68 public static final String ALL_REPOSITORIES = "All Repositories";
70 public static final String BLANK = "blank";
72 private static final String[] datePatterns =
73 new String[]{ "MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
74 "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy" };
76 public static final String SEND_FILE = "send-file";
78 private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
84 private ArchivaConfiguration archivaConfiguration;
90 private RepositoryStatisticsManager repositoryStatisticsManager;
92 private String groupId;
94 private String repositoryId;
98 private int rowCount = 100;
100 private List<String> selectedRepositories = new ArrayList<String>();
102 private String startDate;
104 private String endDate;
106 private int numPages;
108 private Collection<String> repositoryIds;
110 private Map<String, List<RepositoryProblemFacet>> repositoriesMap =
111 new TreeMap<String, List<RepositoryProblemFacet>>();
113 private List<String> availableRepositories;
115 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
117 private InputStream inputStream;
119 private boolean lastPage;
121 @SuppressWarnings( "unchecked" )
122 public void prepare()
124 repositoryIds = new ArrayList<String>();
125 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
126 repositoryIds.addAll( getObservableRepos() );
128 availableRepositories = new ArrayList<String>();
130 // remove selected repositories in the option for the statistics report
131 availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
132 for ( String repo : selectedRepositories )
134 if ( availableRepositories.contains( repo ) )
136 availableRepositories.remove( repo );
142 * Generate the statistics report.
144 * check whether single repo report or comparison report
145 * 1. if it is a single repository, get all the statistics for the repository on the specified date
146 * - if no date is specified, get only the latest
147 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
148 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
149 * - required params: repository, startDate, endDate
151 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
152 * - if no date is specified, use the current date endDate
153 * - required params: repositories, endDate
154 * - total pages = repositories / rows per page
156 * @return action result
158 public String generateStatistics()
162 // TODO: move to validation framework
163 addFieldError( "rowCount", "Row count must be larger than 10." );
169 RepositorySession repositorySession = repositorySessionFactory.createSession();
172 MetadataRepository metadataRepository = repositorySession.getRepository();
173 if ( selectedRepositories.size() > 1 )
179 startDateInDF = getStartDateInDateFormat();
180 endDateInDF = getEndDateInDateFormat();
182 catch ( ParseException e )
184 addActionError( "Error parsing date(s)." );
188 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
190 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
195 for ( String repo : selectedRepositories )
197 List<RepositoryStatistics> stats = null;
201 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
204 catch ( MetadataRepositoryException e )
206 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
208 if ( stats == null || stats.isEmpty() )
210 log.info( "No statistics available for repository '" + repo + "'." );
211 // TODO set repo's stats to 0
215 repositoryStatistics.add( stats.get( 0 ) );
218 else if ( selectedRepositories.size() == 1 )
220 repositoryId = selectedRepositories.get( 0 );
223 startDateInDF = getStartDateInDateFormat();
224 endDateInDF = getEndDateInDateFormat();
226 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
228 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
232 List<RepositoryStatistics> stats = null;
235 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
236 startDateInDF, endDateInDF );
238 catch ( MetadataRepositoryException e )
240 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
242 if ( stats == null || stats.isEmpty() )
245 "No statistics available for repository. Repository might not have been scanned." );
249 int rowCount = getRowCount();
250 int extraPage = ( stats.size() % rowCount ) != 0 ? 1 : 0;
251 int totalPages = ( stats.size() / rowCount ) + extraPage;
252 numPages = totalPages;
254 int currentPage = getPage();
255 if ( currentPage > totalPages )
258 "Error encountered while generating report :: The requested page exceeds the total number of pages." );
262 int start = rowCount * ( currentPage - 1 );
263 int end = ( start + rowCount ) - 1;
265 if ( end > stats.size() )
267 end = stats.size() - 1;
270 repositoryStatistics = stats.subList( start, end + 1 );
272 catch ( ParseException pe )
274 addActionError( pe.getMessage() );
280 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
286 repositorySession.close();
289 if ( repositoryStatistics.isEmpty() )
298 * Export report to CSV.
300 * @return action result
302 public String downloadStatisticsReport()
307 selectedRepositories = parseSelectedRepositories();
308 List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
311 RepositorySession repositorySession = repositorySessionFactory.createSession();
314 MetadataRepository metadataRepository = repositorySession.getRepository();
315 if ( selectedRepositories.size() > 1 )
319 startDateInDF = getStartDateInDateFormat();
320 endDateInDF = getEndDateInDateFormat();
322 catch ( ParseException e )
324 addActionError( "Error parsing date(s)." );
328 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
330 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
334 input = new StringBuffer(
335 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,Archetypes,"
339 for ( String repo : selectedRepositories )
341 List<RepositoryStatistics> stats = null;
345 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
348 catch ( MetadataRepositoryException e )
350 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
352 if ( stats == null || stats.isEmpty() )
354 log.info( "No statistics available for repository '" + repo + "'." );
355 // TODO set repo's stats to 0
359 // only the first one
360 RepositoryStatistics repositoryStats = stats.get( 0 );
361 repositoryStatistics.add( repositoryStats );
363 input.append( repo ).append( "," );
364 input.append( repositoryStats.getTotalFileCount() ).append( "," );
365 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
366 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
367 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
368 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
369 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
370 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
371 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
372 input.append( repositoryStats.getTotalCountForType( "war" ) );
373 input.append( "\n" );
376 else if ( selectedRepositories.size() == 1 )
378 repositoryId = selectedRepositories.get( 0 );
381 startDateInDF = getStartDateInDateFormat();
382 endDateInDF = getEndDateInDateFormat();
384 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
386 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
390 List<RepositoryStatistics> stats = null;
393 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
394 startDateInDF, endDateInDF );
396 catch ( MetadataRepositoryException e )
398 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
400 if ( stats == null || stats.isEmpty() )
403 "No statistics available for repository. Repository might not have been scanned." );
407 input = new StringBuffer(
408 "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,"
409 + "Archetypes,Jars,Wars\n" );
411 for ( RepositoryStatistics repositoryStats : stats )
413 input.append( repositoryStats.getScanStartTime() ).append( "," );
414 input.append( repositoryStats.getTotalFileCount() ).append( "," );
415 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
416 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
417 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
418 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
419 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
420 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
421 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
422 input.append( repositoryStats.getTotalCountForType( "war" ) );
423 input.append( "\n" );
426 repositoryStatistics = stats;
428 catch ( ParseException pe )
430 addActionError( pe.getMessage() );
436 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
442 repositorySession.close();
445 if ( repositoryStatistics.isEmpty() )
450 // write output stream depending on single or comparison report
451 StringReader reader = new StringReader( input.toString() );
455 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
457 catch ( IOException i )
459 addActionError( "Error occurred while generating CSV file." );
466 // hack for parsing the struts list passed as param in <s:url ../>
468 private List<String> parseSelectedRepositories()
470 List<String> parsedSelectedRepos = new ArrayList<String>();
472 for ( String repo : selectedRepositories )
474 String[] tokens = StringUtils.split( repo, ',' );
475 if ( tokens.length > 1 )
477 for ( String token : tokens )
479 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( token, '[' ), ']' ).trim() );
484 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
487 return parsedSelectedRepos;
490 private Date getStartDateInDateFormat()
491 throws ParseException
494 if ( startDate == null || "".equals( startDate ) )
496 startDateInDF = null;
500 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
502 return startDateInDF;
505 private Date getEndDateInDateFormat()
506 throws ParseException
509 if ( endDate == null || "".equals( endDate ) )
515 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
517 // add a day, since we don't inclue time and want the date to be inclusive
518 Calendar cal = Calendar.getInstance();
519 cal.setTime( endDateInDF );
520 cal.add( Calendar.DAY_OF_MONTH, 1 );
521 endDateInDF = cal.getTime();
527 public String execute()
530 if ( repositoryId == null )
532 addFieldError( "repositoryId", "You must provide a repository id." );
538 addFieldError( "rowCount", "Row count must be larger than 10." );
542 List<String> observableRepos = getObservableRepos();
543 Collection<String> repoIds;
544 if ( StringUtils.isEmpty( repositoryId ) || ALL_REPOSITORIES.equals( repositoryId ) )
546 repoIds = observableRepos;
548 else if ( observableRepos.contains( repositoryId ) )
550 repoIds = Collections.singletonList( repositoryId );
554 repoIds = Collections.emptyList();
557 List<RepositoryProblemFacet> problemArtifacts = new ArrayList<RepositoryProblemFacet>();
558 RepositorySession repositorySession = repositorySessionFactory.createSession();
561 MetadataRepository metadataRepository = repositorySession.getRepository();
562 for ( String repoId : repoIds )
564 // TODO: improve performance by navigating into a group subtree. Currently group is property, not part of name of item
565 for ( String name : metadataRepository.getMetadataFacets( repoId, RepositoryProblemFacet.FACET_ID ) )
567 RepositoryProblemFacet metadataFacet =
568 (RepositoryProblemFacet) metadataRepository.getMetadataFacet( repoId,
569 RepositoryProblemFacet.FACET_ID,
572 if ( StringUtils.isEmpty( groupId ) || groupId.equals( metadataFacet.getNamespace() ) )
574 problemArtifacts.add( metadataFacet );
581 repositorySession.close();
584 // TODO: getting range only after reading is not efficient for a large number of artifacts
585 int lowerBound = ( page - 1 ) * rowCount;
586 int upperBound = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
587 if ( upperBound <= problemArtifacts.size() )
589 problemArtifacts = problemArtifacts.subList( lowerBound, upperBound );
593 problemArtifacts = problemArtifacts.subList( lowerBound, problemArtifacts.size() );
596 for ( RepositoryProblemFacet problem : problemArtifacts )
598 List<RepositoryProblemFacet> problemsList;
599 if ( repositoriesMap.containsKey( problem.getRepositoryId() ) )
601 problemsList = repositoriesMap.get( problem.getRepositoryId() );
605 problemsList = new ArrayList<RepositoryProblemFacet>();
606 repositoriesMap.put( problem.getRepositoryId(), problemsList );
609 problemsList.add( problem );
612 // TODO: handling should be improved
613 if ( problemArtifacts.size() <= rowCount )
618 if ( problemArtifacts.isEmpty() && page == 1 )
628 public SecureActionBundle getSecureActionBundle()
629 throws SecureActionException
631 SecureActionBundle bundle = new SecureActionBundle();
633 bundle.setRequiresAuthentication( true );
634 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
639 public Collection<String> getRepositoryIds()
641 return repositoryIds;
644 public String getGroupId()
649 public void setGroupId( String groupId )
651 this.groupId = groupId;
654 public String getRepositoryId()
659 public void setRepositoryId( String repositoryId )
661 this.repositoryId = repositoryId;
669 public void setPage( int page )
674 public int getRowCount()
679 public void setRowCount( int rowCount )
681 this.rowCount = rowCount;
684 public void setRepositoriesMap( Map<String, List<RepositoryProblemFacet>> repositoriesMap )
686 this.repositoriesMap = repositoriesMap;
689 public Map<String, List<RepositoryProblemFacet>> getRepositoriesMap()
691 return repositoriesMap;
694 public List<String> getSelectedRepositories()
696 return selectedRepositories;
699 public void setSelectedRepositories( List<String> selectedRepositories )
701 this.selectedRepositories = selectedRepositories;
704 public List<String> getAvailableRepositories()
706 return availableRepositories;
709 public void setAvailableRepositories( List<String> availableRepositories )
711 this.availableRepositories = availableRepositories;
714 public String getStartDate()
719 public void setStartDate( String startDate )
721 this.startDate = startDate;
724 public String getEndDate()
729 public void setEndDate( String endDate )
731 this.endDate = endDate;
734 public List<RepositoryStatistics> getRepositoryStatistics()
736 return repositoryStatistics;
739 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
741 this.repositoryStatistics = repositoryStatistics;
744 public boolean isLastPage()
749 public void setLastPage( boolean lastPage )
751 this.lastPage = lastPage;
754 public InputStream getInputStream()
759 public int getNumPages()
764 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
766 this.repositoryStatisticsManager = repositoryStatisticsManager;