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.archiva.security.common.ArchivaRoleConstants;
30 import org.apache.commons.io.IOUtils;
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.commons.lang.time.DateUtils;
33 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
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 );
81 private ArchivaConfiguration archivaConfiguration;
84 private RepositoryStatisticsManager repositoryStatisticsManager;
86 private String groupId;
88 private String repositoryId;
92 private int rowCount = 100;
94 private List<String> selectedRepositories = new ArrayList<String>();
96 private String startDate;
98 private String endDate;
100 private int numPages;
102 private Collection<String> repositoryIds;
104 private Map<String, List<RepositoryProblemFacet>> repositoriesMap =
105 new TreeMap<String, List<RepositoryProblemFacet>>();
107 private List<String> availableRepositories;
109 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
111 private InputStream inputStream;
113 private boolean lastPage;
115 @SuppressWarnings( "unchecked" )
116 public void prepare()
118 repositoryIds = new ArrayList<String>();
119 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
120 repositoryIds.addAll( getObservableRepos() );
122 availableRepositories = new ArrayList<String>();
124 // remove selected repositories in the option for the statistics report
125 availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
126 for ( String repo : selectedRepositories )
128 if ( availableRepositories.contains( repo ) )
130 availableRepositories.remove( repo );
136 * Generate the statistics report.
138 * check whether single repo report or comparison report
139 * 1. if it is a single repository, get all the statistics for the repository on the specified date
140 * - if no date is specified, get only the latest
141 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
142 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
143 * - required params: repository, startDate, endDate
145 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
146 * - if no date is specified, use the current date endDate
147 * - required params: repositories, endDate
148 * - total pages = repositories / rows per page
150 * @return action result
152 public String generateStatistics()
156 // TODO: move to validation framework
157 addFieldError( "rowCount", "Row count must be larger than 10." );
163 RepositorySession repositorySession = repositorySessionFactory.createSession();
166 MetadataRepository metadataRepository = repositorySession.getRepository();
167 if ( selectedRepositories.size() > 1 )
173 startDateInDF = getStartDateInDateFormat();
174 endDateInDF = getEndDateInDateFormat();
176 catch ( ParseException e )
178 addActionError( "Error parsing date(s)." );
182 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
184 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
189 for ( String repo : selectedRepositories )
191 List<RepositoryStatistics> stats = null;
195 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
198 catch ( MetadataRepositoryException e )
200 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
202 if ( stats == null || stats.isEmpty() )
204 log.info( "No statistics available for repository '" + repo + "'." );
205 // TODO set repo's stats to 0
209 repositoryStatistics.add( stats.get( 0 ) );
212 else if ( selectedRepositories.size() == 1 )
214 repositoryId = selectedRepositories.get( 0 );
217 startDateInDF = getStartDateInDateFormat();
218 endDateInDF = getEndDateInDateFormat();
220 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
222 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
226 List<RepositoryStatistics> stats = null;
229 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
230 startDateInDF, endDateInDF );
232 catch ( MetadataRepositoryException e )
234 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
236 if ( stats == null || stats.isEmpty() )
239 "No statistics available for repository. Repository might not have been scanned." );
243 int rowCount = getRowCount();
244 int extraPage = ( stats.size() % rowCount ) != 0 ? 1 : 0;
245 int totalPages = ( stats.size() / rowCount ) + extraPage;
246 numPages = totalPages;
248 int currentPage = getPage();
249 if ( currentPage > totalPages )
252 "Error encountered while generating report :: The requested page exceeds the total number of pages." );
256 int start = rowCount * ( currentPage - 1 );
257 int end = ( start + rowCount ) - 1;
259 if ( end >= stats.size() )
261 end = stats.size() - 1;
264 repositoryStatistics = stats.subList( start, end + 1 );
266 catch ( ParseException pe )
268 addActionError( pe.getMessage() );
274 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
280 repositorySession.close();
283 if ( repositoryStatistics.isEmpty() )
292 * Export report to CSV.
294 * @return action result
296 public String downloadStatisticsReport()
301 selectedRepositories = parseSelectedRepositories();
302 List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
305 RepositorySession repositorySession = repositorySessionFactory.createSession();
308 MetadataRepository metadataRepository = repositorySession.getRepository();
309 if ( selectedRepositories.size() > 1 )
313 startDateInDF = getStartDateInDateFormat();
314 endDateInDF = getEndDateInDateFormat();
316 catch ( ParseException e )
318 addActionError( "Error parsing date(s)." );
322 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
324 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
328 input = new StringBuilder(
329 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,Archetypes,"
333 for ( String repo : selectedRepositories )
335 List<RepositoryStatistics> stats = null;
339 repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repo, startDateInDF,
342 catch ( MetadataRepositoryException e )
344 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
346 if ( stats == null || stats.isEmpty() )
348 log.info( "No statistics available for repository '" + repo + "'." );
349 // TODO set repo's stats to 0
353 // only the first one
354 RepositoryStatistics repositoryStats = stats.get( 0 );
355 repositoryStatistics.add( repositoryStats );
357 input.append( repo ).append( "," );
358 input.append( repositoryStats.getTotalFileCount() ).append( "," );
359 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
360 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
361 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
362 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
363 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
364 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
365 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
366 input.append( repositoryStats.getTotalCountForType( "war" ) );
367 input.append( "\n" );
370 else if ( selectedRepositories.size() == 1 )
372 repositoryId = selectedRepositories.get( 0 );
375 startDateInDF = getStartDateInDateFormat();
376 endDateInDF = getEndDateInDateFormat();
378 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
380 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
384 List<RepositoryStatistics> stats = null;
387 stats = repositoryStatisticsManager.getStatisticsInRange( metadataRepository, repositoryId,
388 startDateInDF, endDateInDF );
390 catch ( MetadataRepositoryException e )
392 log.warn( "Unable to retrieve stats, assuming is empty: " + e.getMessage(), e );
394 if ( stats == null || stats.isEmpty() )
397 "No statistics available for repository. Repository might not have been scanned." );
401 input = new StringBuilder(
402 "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count,Plugins,"
403 + "Archetypes,Jars,Wars\n" );
405 for ( RepositoryStatistics repositoryStats : stats )
407 input.append( repositoryStats.getScanStartTime() ).append( "," );
408 input.append( repositoryStats.getTotalFileCount() ).append( "," );
409 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
410 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
411 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
412 input.append( repositoryStats.getTotalProjectCount() ).append( "," );
413 input.append( repositoryStats.getTotalCountForType( "maven-plugin" ) ).append( "," );
414 input.append( repositoryStats.getTotalCountForType( "maven-archetype" ) ).append( "," );
415 input.append( repositoryStats.getTotalCountForType( "jar" ) ).append( "," );
416 input.append( repositoryStats.getTotalCountForType( "war" ) );
417 input.append( "\n" );
420 repositoryStatistics = stats;
422 catch ( ParseException pe )
424 addActionError( pe.getMessage() );
430 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
436 repositorySession.close();
439 if ( repositoryStatistics.isEmpty() )
444 // write output stream depending on single or comparison report
445 StringReader reader = new StringReader( input.toString() );
449 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
451 catch ( IOException i )
453 addActionError( "Error occurred while generating CSV file." );
460 // hack for parsing the struts list passed as param in <s:url ../>
462 private List<String> parseSelectedRepositories()
464 List<String> parsedSelectedRepos = new ArrayList<String>();
466 for ( String repo : selectedRepositories )
468 String[] tokens = StringUtils.split( repo, ',' );
469 if ( tokens.length > 1 )
471 for ( String token : tokens )
473 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( token, '[' ), ']' ).trim() );
478 parsedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
481 return parsedSelectedRepos;
484 private Date getStartDateInDateFormat()
485 throws ParseException
488 if ( startDate == null || "".equals( startDate ) )
490 startDateInDF = null;
494 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
496 return startDateInDF;
499 private Date getEndDateInDateFormat()
500 throws ParseException
503 if ( endDate == null || "".equals( endDate ) )
509 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
511 // add a day, since we don't inclue time and want the date to be inclusive
512 Calendar cal = Calendar.getInstance();
513 cal.setTime( endDateInDF );
514 cal.add( Calendar.DAY_OF_MONTH, 1 );
515 endDateInDF = cal.getTime();
521 public String execute()
524 if ( repositoryId == null )
526 addFieldError( "repositoryId", "You must provide a repository id." );
532 addFieldError( "rowCount", "Row count must be larger than 10." );
536 List<String> observableRepos = getObservableRepos();
537 Collection<String> repoIds;
538 if ( StringUtils.isEmpty( repositoryId ) || ALL_REPOSITORIES.equals( repositoryId ) )
540 repoIds = observableRepos;
542 else if ( observableRepos.contains( repositoryId ) )
544 repoIds = Collections.singletonList( repositoryId );
548 repoIds = Collections.emptyList();
551 List<RepositoryProblemFacet> problemArtifacts = new ArrayList<RepositoryProblemFacet>();
552 RepositorySession repositorySession = repositorySessionFactory.createSession();
555 MetadataRepository metadataRepository = repositorySession.getRepository();
556 for ( String repoId : repoIds )
558 // TODO: improve performance by navigating into a group subtree. Currently group is property, not part of name of item
559 for ( String name : metadataRepository.getMetadataFacets( repoId, RepositoryProblemFacet.FACET_ID ) )
561 RepositoryProblemFacet metadataFacet =
562 (RepositoryProblemFacet) metadataRepository.getMetadataFacet( repoId,
563 RepositoryProblemFacet.FACET_ID,
566 if ( StringUtils.isEmpty( groupId ) || groupId.equals( metadataFacet.getNamespace() ) )
568 problemArtifacts.add( metadataFacet );
575 repositorySession.close();
578 // TODO: getting range only after reading is not efficient for a large number of artifacts
579 int lowerBound = ( page - 1 ) * rowCount;
580 int upperBound = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
581 if ( upperBound <= problemArtifacts.size() )
583 problemArtifacts = problemArtifacts.subList( lowerBound, upperBound );
587 problemArtifacts = problemArtifacts.subList( lowerBound, problemArtifacts.size() );
590 for ( RepositoryProblemFacet problem : problemArtifacts )
592 List<RepositoryProblemFacet> problemsList;
593 if ( repositoriesMap.containsKey( problem.getRepositoryId() ) )
595 problemsList = repositoriesMap.get( problem.getRepositoryId() );
599 problemsList = new ArrayList<RepositoryProblemFacet>();
600 repositoriesMap.put( problem.getRepositoryId(), problemsList );
603 problemsList.add( problem );
606 // TODO: handling should be improved
607 if ( problemArtifacts.size() <= rowCount )
612 if ( problemArtifacts.isEmpty() && page == 1 )
622 public SecureActionBundle getSecureActionBundle()
623 throws SecureActionException
625 SecureActionBundle bundle = new SecureActionBundle();
627 bundle.setRequiresAuthentication( true );
628 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
633 public Collection<String> getRepositoryIds()
635 return repositoryIds;
638 public String getGroupId()
643 public void setGroupId( String groupId )
645 this.groupId = groupId;
648 public String getRepositoryId()
653 public void setRepositoryId( String repositoryId )
655 this.repositoryId = repositoryId;
663 public void setPage( int page )
668 public int getRowCount()
673 public void setRowCount( int rowCount )
675 this.rowCount = rowCount;
678 public void setRepositoriesMap( Map<String, List<RepositoryProblemFacet>> repositoriesMap )
680 this.repositoriesMap = repositoriesMap;
683 public Map<String, List<RepositoryProblemFacet>> getRepositoriesMap()
685 return repositoriesMap;
688 public List<String> getSelectedRepositories()
690 return selectedRepositories;
693 public void setSelectedRepositories( List<String> selectedRepositories )
695 this.selectedRepositories = selectedRepositories;
698 public List<String> getAvailableRepositories()
700 return availableRepositories;
703 public void setAvailableRepositories( List<String> availableRepositories )
705 this.availableRepositories = availableRepositories;
708 public String getStartDate()
713 public void setStartDate( String startDate )
715 this.startDate = startDate;
718 public String getEndDate()
723 public void setEndDate( String endDate )
725 this.endDate = endDate;
728 public List<RepositoryStatistics> getRepositoryStatistics()
730 return repositoryStatistics;
733 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
735 this.repositoryStatistics = repositoryStatistics;
738 public boolean isLastPage()
743 public void setLastPage( boolean lastPage )
745 this.lastPage = lastPage;
748 public InputStream getInputStream()
753 public int getNumPages()
758 public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
760 this.repositoryStatisticsManager = repositoryStatisticsManager;