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;
24 import org.apache.commons.io.IOUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.commons.lang.time.DateUtils;
27 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
28 import org.apache.maven.archiva.database.ArchivaDAO;
29 import org.apache.maven.archiva.database.ArchivaDatabaseException;
30 import org.apache.maven.archiva.database.Constraint;
31 import org.apache.maven.archiva.database.ObjectNotFoundException;
32 import org.apache.maven.archiva.database.RepositoryContentStatisticsDAO;
33 import org.apache.maven.archiva.database.constraints.RangeConstraint;
34 import org.apache.maven.archiva.database.constraints.RepositoryContentStatisticsByRepositoryConstraint;
35 import org.apache.maven.archiva.database.constraints.RepositoryProblemByGroupIdConstraint;
36 import org.apache.maven.archiva.database.constraints.RepositoryProblemByRepositoryIdConstraint;
37 import org.apache.maven.archiva.database.constraints.RepositoryProblemConstraint;
38 import org.apache.maven.archiva.database.constraints.UniqueFieldConstraint;
39 import org.apache.maven.archiva.model.RepositoryContentStatistics;
40 import org.apache.maven.archiva.model.RepositoryProblem;
41 import org.apache.maven.archiva.model.RepositoryProblemReport;
42 import org.apache.maven.archiva.reporting.ArchivaReportException;
43 import org.apache.maven.archiva.reporting.DataLimits;
44 import org.apache.maven.archiva.reporting.RepositoryStatistics;
45 import org.apache.maven.archiva.reporting.RepositoryStatisticsReportGenerator;
46 import org.apache.maven.archiva.security.ArchivaRoleConstants;
47 import org.codehaus.plexus.redback.rbac.Resource;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
51 import javax.servlet.http.HttpServletRequest;
53 import java.io.ByteArrayInputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.StringReader;
57 import java.text.ParseException;
58 import java.util.ArrayList;
59 import java.util.Calendar;
60 import java.util.Collection;
61 import java.util.Date;
62 import java.util.List;
64 import java.util.TreeMap;
65 import org.apache.maven.archiva.web.action.PlexusActionSupport;
66 import org.apache.struts2.interceptor.ServletRequestAware;
67 import org.codehaus.redback.integration.interceptor.SecureAction;
68 import org.codehaus.redback.integration.interceptor.SecureActionBundle;
69 import org.codehaus.redback.integration.interceptor.SecureActionException;
72 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="generateReport" instantiation-strategy="per-lookup"
74 public class GenerateReportAction
75 extends PlexusActionSupport
76 implements SecureAction, ServletRequestAware, Preparable
78 private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
81 * @plexus.requirement role-hint="jdo"
83 protected ArchivaDAO dao;
88 private ArchivaConfiguration archivaConfiguration;
90 protected Constraint constraint;
92 protected HttpServletRequest request;
94 protected List<RepositoryProblemReport> reports = new ArrayList<RepositoryProblemReport>();
96 protected String groupId;
98 protected String repositoryId;
100 protected String prev;
102 protected String next;
104 protected int[] range = new int[2];
106 protected int page = 1;
108 protected int rowCount = 100;
110 protected boolean isLastPage;
112 public static final String BLANK = "blank";
114 public static final String BASIC = "basic";
116 private static Boolean jasperPresent;
118 private Collection<String> repositoryIds;
120 public static final String ALL_REPOSITORIES = "All Repositories";
122 protected Map<String, List<RepositoryProblemReport>> repositoriesMap =
123 new TreeMap<String, List<RepositoryProblemReport>>();
125 // for statistics report
127 * @plexus.requirement role-hint="simple"
129 private RepositoryStatisticsReportGenerator generator;
131 private List<String> selectedRepositories = new ArrayList<String>();
133 private List<String> availableRepositories;
135 private String startDate;
137 private String endDate;
139 private int reposSize;
141 private String selectedRepo;
143 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
145 private DataLimits limits = new DataLimits();
147 private String[] datePatterns = new String[] { "MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy",
148 "dd MMMMM yyyy", "dd/MM/yy", "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy",
151 public static final String SEND_FILE = "send-file";
153 private InputStream inputStream;
155 public void prepare()
157 repositoryIds = new ArrayList<String>();
158 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
159 repositoryIds.addAll(
160 dao.query( new UniqueFieldConstraint( RepositoryProblem.class.getName(), "repositoryId" ) ) );
162 availableRepositories = new ArrayList<String>();
164 // remove selected repositories in the option for the statistics report
165 availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
166 for( String repo : selectedRepositories )
168 if( availableRepositories.contains( repo ) )
170 availableRepositories.remove( repo );
175 public Collection<String> getRepositoryIds()
177 return repositoryIds;
181 * Generate the statistics report.
183 * check whether single repo report or comparison report
184 * 1. if it is a single repository, get all the statistics for the repository on the specified date
185 * - if no date is specified, get only the latest
186 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
187 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
188 * - required params: repository, startDate, endDate
190 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
191 * - if no date is specified, use the current date endDate
192 * - required params: repositories, endDate
193 * - total pages = repositories / rows per page
197 public String generateStatistics()
201 addFieldError( "rowCount", "Row count must be larger than 10." );
204 reposSize = selectedRepositories.size();
208 RepositoryContentStatisticsDAO repoContentStatsDao = dao.getRepositoryContentStatisticsDAO();
209 Date startDateInDF = null;
210 Date endDateInDF = null;
212 if( selectedRepositories.size() > 1 )
214 limits.setTotalCount( selectedRepositories.size() );
215 limits.setCurrentPage( 1 );
216 limits.setPerPageCount( 1 );
217 limits.setCountOfPages( 1 );
221 startDateInDF = getStartDateInDateFormat();
222 endDateInDF = getEndDateInDateFormat();
224 catch ( ParseException e )
226 addActionError( "Error parsing date(s)." );
231 generateReportForMultipleRepos(repoContentStatsDao, startDateInDF, endDateInDF, true);
233 else if ( selectedRepositories.size() == 1 )
235 limits.setCurrentPage( getPage() );
236 limits.setPerPageCount( getRowCount() );
238 selectedRepo = selectedRepositories.get( 0 );
241 startDateInDF = getStartDateInDateFormat();
242 endDateInDF = getEndDateInDateFormat();
244 List<RepositoryContentStatistics> contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
245 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF, endDateInDF ) );
247 if( contentStats == null || contentStats.isEmpty() )
249 addActionError( "No statistics available for repository. Repository might not have been scanned." );
253 limits.setTotalCount( contentStats.size() );
254 int extraPage = ( limits.getTotalCount() % limits.getPerPageCount() ) != 0 ? 1 : 0;
255 int totalPages = ( limits.getTotalCount() / limits.getPerPageCount() ) + extraPage;
256 limits.setCountOfPages( totalPages );
258 repositoryStatistics = generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, limits );
260 catch ( ObjectNotFoundException oe )
262 addActionError( oe.getMessage() );
265 catch ( ArchivaDatabaseException de )
267 addActionError( de.getMessage() );
270 catch ( ParseException pe )
272 addActionError( pe.getMessage() );
278 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
282 if( repositoryStatistics.isEmpty() )
287 catch ( ArchivaReportException e )
289 addActionError( "Error encountered while generating report :: " + e.getMessage() );
297 * Export report to CSV.
301 public String downloadStatisticsReport()
305 Date startDateInDF = null;
306 Date endDateInDF = null;
308 selectedRepositories = parseSelectedRepositories();
309 repositoryStatistics = new ArrayList<RepositoryStatistics>();
311 RepositoryContentStatisticsDAO repoContentStatsDao = dao.getRepositoryContentStatisticsDAO();
312 if( selectedRepositories.size() > 1 )
316 startDateInDF = getStartDateInDateFormat();
317 endDateInDF = getEndDateInDateFormat();
319 catch ( ParseException e )
321 addActionError( "Error parsing date(s)." );
326 generateReportForMultipleRepos( repoContentStatsDao, startDateInDF, endDateInDF, false );
328 else if ( selectedRepositories.size() == 1 )
330 selectedRepo = selectedRepositories.get( 0 );
333 startDateInDF = getStartDateInDateFormat();
334 endDateInDF = getEndDateInDateFormat();
336 List<RepositoryContentStatistics> contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
337 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF, endDateInDF ) );
339 if( contentStats == null || contentStats.isEmpty() )
341 addActionError( "No statistics available for repository. Repository might not have been scanned." );
345 repositoryStatistics = generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, false );
347 catch ( ObjectNotFoundException oe )
349 addActionError( oe.getMessage() );
352 catch ( ArchivaDatabaseException de )
354 addActionError( de.getMessage() );
357 catch ( ParseException pe )
359 addActionError( pe.getMessage() );
365 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
369 if( repositoryStatistics.isEmpty() )
374 catch ( ArchivaReportException e )
376 addActionError( "Error encountered while generating report :: " + e.getMessage() );
380 // write output stream depending on single or comparison report
381 StringBuffer input = getInput();
382 StringReader reader = new StringReader( input.toString() );
386 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
388 catch ( IOException i )
390 addActionError( "Error occurred while generating CSV file." );
397 // hack for parsing the struts list passed as param in <s:url ../>
398 private List<String> parseSelectedRepositories()
400 List<String> pasedSelectedRepos = new ArrayList<String>();
402 for( String repo : selectedRepositories )
404 String[] tokens = StringUtils.split( repo, ',' );
405 if( tokens.length > 1 )
407 for( int i = 0; i < tokens.length; i++ )
409 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( tokens[i], '[' ), ']' ).trim() );
414 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
417 return pasedSelectedRepos;
420 private void generateReportForMultipleRepos( RepositoryContentStatisticsDAO repoContentStatsDao,
421 Date startDateInDF, Date endDateInDF, boolean useLimits )
422 throws ArchivaReportException
424 for ( String repo : selectedRepositories )
428 List contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
429 new RepositoryContentStatisticsByRepositoryConstraint( repo, startDateInDF, endDateInDF ) );
431 if ( contentStats == null || contentStats.isEmpty() )
433 log.info( "No statistics available for repository '" + repo + "'." );
434 // TODO set repo's stats to 0
440 repositoryStatistics.addAll( generator.generateReport( contentStats, repo, startDateInDF, endDateInDF,
445 repositoryStatistics.addAll( generator.generateReport( contentStats, repo, startDateInDF, endDateInDF, true ) );
448 catch ( ObjectNotFoundException oe )
450 log.error( "No statistics available for repository '" + repo + "'." );
451 // TODO set repo's stats to 0
453 catch ( ArchivaDatabaseException ae )
455 log.error( "Error encountered while querying statistics of repository '" + repo + "'." );
456 // TODO set repo's stats to 0
461 private Date getStartDateInDateFormat()
462 throws ParseException
465 if ( startDate == null || "".equals( startDate ) )
467 startDateInDF = getDefaultStartDate();
471 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
473 return startDateInDF;
476 private Date getEndDateInDateFormat()
477 throws ParseException
480 if ( endDate == null || "".equals( endDate ) )
482 endDateInDF = getDefaultEndDate();
486 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
492 private StringBuffer getInput()
494 StringBuffer input = null;
496 if( selectedRepositories.size() == 1 )
498 input = new StringBuffer( "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
499 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
501 for( RepositoryStatistics stats : repositoryStatistics )
503 input.append( stats.getDateOfScan() ).append( "," );
504 input.append( stats.getFileCount() ).append( "," );
505 input.append( stats.getTotalSize() ).append( "," );
506 input.append( stats.getArtifactCount() ).append( "," );
507 input.append( stats.getGroupCount() ).append( "," );
508 input.append( stats.getProjectCount() ).append( "," );
509 input.append( stats.getPluginCount() ).append( "," );
510 input.append( stats.getArchetypeCount() ).append( "," );
511 input.append( stats.getJarCount() ).append( "," );
512 input.append( stats.getWarCount() ).append( "," );
513 input.append( stats.getDeploymentCount() ).append( "," );
514 input.append( stats.getDownloadCount() ).append( "\n" );
517 else if( selectedRepositories.size() > 1 )
519 input = new StringBuffer( "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
520 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
522 for( RepositoryStatistics stats : repositoryStatistics )
524 input.append( stats.getRepositoryId() ).append( "," );
525 input.append( stats.getFileCount() ).append( "," );
526 input.append( stats.getTotalSize() ).append( "," );
527 input.append( stats.getArtifactCount() ).append( "," );
528 input.append( stats.getGroupCount() ).append( "," );
529 input.append( stats.getProjectCount() ).append( "," );
530 input.append( stats.getPluginCount() ).append( "," );
531 input.append( stats.getArchetypeCount() ).append( "," );
532 input.append( stats.getJarCount() ).append( "," );
533 input.append( stats.getWarCount() ).append( "," );
534 input.append( stats.getDeploymentCount() ).append( "," );
535 input.append( stats.getDownloadCount() ).append( "\n" );
542 private Date getDefaultStartDate()
544 Calendar cal = Calendar.getInstance();
546 cal.set( 1900, 1, 1, 0, 0, 0 );
548 return cal.getTime();
551 private Date getDefaultEndDate()
553 return Calendar.getInstance().getTime();
556 public String execute()
559 if( repositoryId == null )
561 addFieldError( "repositoryId", "You must provide a repository id.");
567 addFieldError( "rowCount", "Row count must be larger than 10." );
571 List<RepositoryProblem> problemArtifacts =
572 dao.getRepositoryProblemDAO().queryRepositoryProblems( configureConstraint() );
575 request.getRequestURL().substring( 0, request.getRequestURL().indexOf( request.getRequestURI() ) );
576 RepositoryProblem problemArtifact;
577 RepositoryProblemReport problemArtifactReport;
578 for ( int i = 0; i < problemArtifacts.size(); i++ )
580 problemArtifact = (RepositoryProblem) problemArtifacts.get( i );
581 problemArtifactReport = new RepositoryProblemReport( problemArtifact );
583 problemArtifactReport.setGroupURL( contextPath + "/browse/" + problemArtifact.getGroupId() );
584 problemArtifactReport.setArtifactURL(
585 contextPath + "/browse/" + problemArtifact.getGroupId() + "/" + problemArtifact.getArtifactId() );
587 addToList( problemArtifactReport );
589 // retained the reports list because this is the datasource for the jasper report
590 reports.add( problemArtifactReport );
593 if ( reports.size() <= rowCount )
599 reports.remove( rowCount );
602 prev = request.getRequestURL() + "?page=" + ( page - 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
603 "&repositoryId=" + repositoryId;
604 next = request.getRequestURL() + "?page=" + ( page + 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
605 "&repositoryId=" + repositoryId;
607 if ( reports.size() == 0 && page == 1 )
611 else if ( isJasperPresent() )
621 private static boolean isJasperPresent()
623 if ( jasperPresent == null )
627 Class.forName( "net.sf.jasperreports.engine.JRExporterParameter" );
628 jasperPresent = Boolean.TRUE;
630 catch ( NoClassDefFoundError e )
632 jasperPresent = Boolean.FALSE;
634 catch ( ClassNotFoundException e )
636 jasperPresent = Boolean.FALSE;
639 return jasperPresent.booleanValue();
642 private Constraint configureConstraint()
644 Constraint constraint;
646 range[0] = ( page - 1 ) * rowCount;
647 range[1] = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
649 if ( groupId != null && ( !groupId.equals( "" ) ) )
651 if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
653 constraint = new RepositoryProblemConstraint( range, groupId, repositoryId );
657 constraint = new RepositoryProblemByGroupIdConstraint( range, groupId );
660 else if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
662 constraint = new RepositoryProblemByRepositoryIdConstraint( range, repositoryId );
666 constraint = new RangeConstraint( range, "repositoryId" );
672 public SecureActionBundle getSecureActionBundle()
673 throws SecureActionException
675 SecureActionBundle bundle = new SecureActionBundle();
677 bundle.setRequiresAuthentication( true );
678 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
683 private void addToList( RepositoryProblemReport repoProblemReport )
685 List<RepositoryProblemReport> problemsList = null;
687 if ( repositoriesMap.containsKey( repoProblemReport.getRepositoryId() ) )
689 problemsList = ( List<RepositoryProblemReport> ) repositoriesMap.get( repoProblemReport.getRepositoryId() );
693 problemsList = new ArrayList<RepositoryProblemReport>();
694 repositoriesMap.put( repoProblemReport.getRepositoryId(), problemsList );
697 problemsList.add( repoProblemReport );
700 public void setServletRequest( HttpServletRequest request )
702 this.request = request;
705 public List<RepositoryProblemReport> getReports()
710 public String getGroupId()
715 public void setGroupId( String groupId )
717 this.groupId = groupId;
720 public String getRepositoryId()
725 public void setRepositoryId( String repositoryId )
727 this.repositoryId = repositoryId;
730 public String getPrev()
735 public String getNext()
745 public void setPage( int page )
750 public int getRowCount()
755 public void setRowCount( int rowCount )
757 this.rowCount = rowCount;
760 public boolean getIsLastPage()
765 public void setRepositoriesMap( Map<String, List<RepositoryProblemReport>> repositoriesMap )
767 this.repositoriesMap = repositoriesMap;
770 public Map<String, List<RepositoryProblemReport>> getRepositoriesMap()
772 return repositoriesMap;
775 public List<String> getSelectedRepositories()
777 return selectedRepositories;
780 public void setSelectedRepositories( List<String> selectedRepositories )
782 this.selectedRepositories = selectedRepositories;
785 public List<String> getAvailableRepositories()
787 return availableRepositories;
790 public void setAvailableRepositories( List<String> availableRepositories )
792 this.availableRepositories = availableRepositories;
795 public String getStartDate()
800 public void setStartDate( String startDate )
802 this.startDate = startDate;
805 public String getEndDate()
810 public void setEndDate( String endDate )
812 this.endDate = endDate;
815 public List<RepositoryStatistics> getRepositoryStatistics()
817 return repositoryStatistics;
820 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
822 this.repositoryStatistics = repositoryStatistics;
825 public int getReposSize()
830 public void setReposSize( int reposSize )
832 this.reposSize = reposSize;
835 public String getSelectedRepo()
840 public void setSelectedRepo( String selectedRepo )
842 this.selectedRepo = selectedRepo;
845 public DataLimits getLimits()
850 public void setLimits( DataLimits limits )
852 this.limits = limits;
855 public InputStream getInputStream()