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)." );
230 if( startDateInDF.after( endDateInDF ) )
232 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
237 generateReportForMultipleRepos(repoContentStatsDao, startDateInDF, endDateInDF, true);
239 else if ( selectedRepositories.size() == 1 )
241 limits.setCurrentPage( getPage() );
242 limits.setPerPageCount( getRowCount() );
244 selectedRepo = selectedRepositories.get( 0 );
247 startDateInDF = getStartDateInDateFormat();
248 endDateInDF = getEndDateInDateFormat();
250 if( startDateInDF.after( endDateInDF ) )
252 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
256 List<RepositoryContentStatistics> contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
257 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF, endDateInDF ) );
259 if( contentStats == null || contentStats.isEmpty() )
261 addActionError( "No statistics available for repository. Repository might not have been scanned." );
265 limits.setTotalCount( contentStats.size() );
266 int extraPage = ( limits.getTotalCount() % limits.getPerPageCount() ) != 0 ? 1 : 0;
267 int totalPages = ( limits.getTotalCount() / limits.getPerPageCount() ) + extraPage;
268 limits.setCountOfPages( totalPages );
270 repositoryStatistics = generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, limits );
272 catch ( ObjectNotFoundException oe )
274 addActionError( oe.getMessage() );
277 catch ( ArchivaDatabaseException de )
279 addActionError( de.getMessage() );
282 catch ( ParseException pe )
284 addActionError( pe.getMessage() );
290 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
294 if( repositoryStatistics.isEmpty() )
299 catch ( ArchivaReportException e )
301 addActionError( "Error encountered while generating report :: " + e.getMessage() );
309 * Export report to CSV.
313 public String downloadStatisticsReport()
317 Date startDateInDF = null;
318 Date endDateInDF = null;
320 selectedRepositories = parseSelectedRepositories();
321 repositoryStatistics = new ArrayList<RepositoryStatistics>();
323 RepositoryContentStatisticsDAO repoContentStatsDao = dao.getRepositoryContentStatisticsDAO();
324 if( selectedRepositories.size() > 1 )
328 startDateInDF = getStartDateInDateFormat();
329 endDateInDF = getEndDateInDateFormat();
331 catch ( ParseException e )
333 addActionError( "Error parsing date(s)." );
337 if( startDateInDF.after( endDateInDF ) )
339 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
344 generateReportForMultipleRepos( repoContentStatsDao, startDateInDF, endDateInDF, false );
346 else if ( selectedRepositories.size() == 1 )
348 selectedRepo = selectedRepositories.get( 0 );
351 startDateInDF = getStartDateInDateFormat();
352 endDateInDF = getEndDateInDateFormat();
354 if( startDateInDF.after( endDateInDF ) )
356 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
360 List<RepositoryContentStatistics> contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
361 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF, endDateInDF ) );
363 if( contentStats == null || contentStats.isEmpty() )
365 addActionError( "No statistics available for repository. Repository might not have been scanned." );
369 repositoryStatistics = generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, false );
371 catch ( ObjectNotFoundException oe )
373 addActionError( oe.getMessage() );
376 catch ( ArchivaDatabaseException de )
378 addActionError( de.getMessage() );
381 catch ( ParseException pe )
383 addActionError( pe.getMessage() );
389 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
393 if( repositoryStatistics.isEmpty() )
398 catch ( ArchivaReportException e )
400 addActionError( "Error encountered while generating report :: " + e.getMessage() );
404 // write output stream depending on single or comparison report
405 StringBuffer input = getInput();
406 StringReader reader = new StringReader( input.toString() );
410 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
412 catch ( IOException i )
414 addActionError( "Error occurred while generating CSV file." );
421 // hack for parsing the struts list passed as param in <s:url ../>
422 private List<String> parseSelectedRepositories()
424 List<String> pasedSelectedRepos = new ArrayList<String>();
426 for( String repo : selectedRepositories )
428 String[] tokens = StringUtils.split( repo, ',' );
429 if( tokens.length > 1 )
431 for( int i = 0; i < tokens.length; i++ )
433 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( tokens[i], '[' ), ']' ).trim() );
438 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
441 return pasedSelectedRepos;
444 private void generateReportForMultipleRepos( RepositoryContentStatisticsDAO repoContentStatsDao,
445 Date startDateInDF, Date endDateInDF, boolean useLimits )
446 throws ArchivaReportException
448 for ( String repo : selectedRepositories )
452 List contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
453 new RepositoryContentStatisticsByRepositoryConstraint( repo, startDateInDF, endDateInDF ) );
455 if ( contentStats == null || contentStats.isEmpty() )
457 log.info( "No statistics available for repository '" + repo + "'." );
458 // TODO set repo's stats to 0
464 repositoryStatistics.addAll( generator.generateReport( contentStats, repo, startDateInDF, endDateInDF,
469 repositoryStatistics.addAll( generator.generateReport( contentStats, repo, startDateInDF, endDateInDF, true ) );
472 catch ( ObjectNotFoundException oe )
474 log.error( "No statistics available for repository '" + repo + "'." );
475 // TODO set repo's stats to 0
477 catch ( ArchivaDatabaseException ae )
479 log.error( "Error encountered while querying statistics of repository '" + repo + "'." );
480 // TODO set repo's stats to 0
485 private Date getStartDateInDateFormat()
486 throws ParseException
489 if ( startDate == null || "".equals( startDate ) )
491 startDateInDF = getDefaultStartDate();
495 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
497 return startDateInDF;
500 private Date getEndDateInDateFormat()
501 throws ParseException
504 if ( endDate == null || "".equals( endDate ) )
506 endDateInDF = getDefaultEndDate();
510 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
516 private StringBuffer getInput()
518 StringBuffer input = null;
520 if( selectedRepositories.size() == 1 )
522 input = new StringBuffer( "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
523 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
525 for( RepositoryStatistics stats : repositoryStatistics )
527 input.append( stats.getDateOfScan() ).append( "," );
528 input.append( stats.getFileCount() ).append( "," );
529 input.append( stats.getTotalSize() ).append( "," );
530 input.append( stats.getArtifactCount() ).append( "," );
531 input.append( stats.getGroupCount() ).append( "," );
532 input.append( stats.getProjectCount() ).append( "," );
533 input.append( stats.getPluginCount() ).append( "," );
534 input.append( stats.getArchetypeCount() ).append( "," );
535 input.append( stats.getJarCount() ).append( "," );
536 input.append( stats.getWarCount() ).append( "," );
537 input.append( stats.getDeploymentCount() ).append( "," );
538 input.append( stats.getDownloadCount() ).append( "\n" );
541 else if( selectedRepositories.size() > 1 )
543 input = new StringBuffer( "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
544 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
546 for( RepositoryStatistics stats : repositoryStatistics )
548 input.append( stats.getRepositoryId() ).append( "," );
549 input.append( stats.getFileCount() ).append( "," );
550 input.append( stats.getTotalSize() ).append( "," );
551 input.append( stats.getArtifactCount() ).append( "," );
552 input.append( stats.getGroupCount() ).append( "," );
553 input.append( stats.getProjectCount() ).append( "," );
554 input.append( stats.getPluginCount() ).append( "," );
555 input.append( stats.getArchetypeCount() ).append( "," );
556 input.append( stats.getJarCount() ).append( "," );
557 input.append( stats.getWarCount() ).append( "," );
558 input.append( stats.getDeploymentCount() ).append( "," );
559 input.append( stats.getDownloadCount() ).append( "\n" );
566 private Date getDefaultStartDate()
568 Calendar cal = Calendar.getInstance();
570 cal.set( 1900, 1, 1, 0, 0, 0 );
572 return cal.getTime();
575 private Date getDefaultEndDate()
577 return Calendar.getInstance().getTime();
580 public String execute()
583 if( repositoryId == null )
585 addFieldError( "repositoryId", "You must provide a repository id.");
591 addFieldError( "rowCount", "Row count must be larger than 10." );
595 List<RepositoryProblem> problemArtifacts =
596 dao.getRepositoryProblemDAO().queryRepositoryProblems( configureConstraint() );
599 request.getRequestURL().substring( 0, request.getRequestURL().indexOf( request.getRequestURI() ) );
600 RepositoryProblem problemArtifact;
601 RepositoryProblemReport problemArtifactReport;
602 for ( int i = 0; i < problemArtifacts.size(); i++ )
604 problemArtifact = (RepositoryProblem) problemArtifacts.get( i );
605 problemArtifactReport = new RepositoryProblemReport( problemArtifact );
607 problemArtifactReport.setGroupURL( contextPath + "/browse/" + problemArtifact.getGroupId() );
608 problemArtifactReport.setArtifactURL(
609 contextPath + "/browse/" + problemArtifact.getGroupId() + "/" + problemArtifact.getArtifactId() );
611 addToList( problemArtifactReport );
613 // retained the reports list because this is the datasource for the jasper report
614 reports.add( problemArtifactReport );
617 if ( reports.size() <= rowCount )
623 reports.remove( rowCount );
626 prev = request.getRequestURL() + "?page=" + ( page - 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
627 "&repositoryId=" + repositoryId;
628 next = request.getRequestURL() + "?page=" + ( page + 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
629 "&repositoryId=" + repositoryId;
631 if ( reports.size() == 0 && page == 1 )
635 else if ( isJasperPresent() )
645 private static boolean isJasperPresent()
647 if ( jasperPresent == null )
651 Class.forName( "net.sf.jasperreports.engine.JRExporterParameter" );
652 jasperPresent = Boolean.TRUE;
654 catch ( NoClassDefFoundError e )
656 jasperPresent = Boolean.FALSE;
658 catch ( ClassNotFoundException e )
660 jasperPresent = Boolean.FALSE;
663 return jasperPresent.booleanValue();
666 private Constraint configureConstraint()
668 Constraint constraint;
670 range[0] = ( page - 1 ) * rowCount;
671 range[1] = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
673 if ( groupId != null && ( !groupId.equals( "" ) ) )
675 if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
677 constraint = new RepositoryProblemConstraint( range, groupId, repositoryId );
681 constraint = new RepositoryProblemByGroupIdConstraint( range, groupId );
684 else if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
686 constraint = new RepositoryProblemByRepositoryIdConstraint( range, repositoryId );
690 constraint = new RangeConstraint( range, "repositoryId" );
696 public SecureActionBundle getSecureActionBundle()
697 throws SecureActionException
699 SecureActionBundle bundle = new SecureActionBundle();
701 bundle.setRequiresAuthentication( true );
702 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
707 private void addToList( RepositoryProblemReport repoProblemReport )
709 List<RepositoryProblemReport> problemsList = null;
711 if ( repositoriesMap.containsKey( repoProblemReport.getRepositoryId() ) )
713 problemsList = ( List<RepositoryProblemReport> ) repositoriesMap.get( repoProblemReport.getRepositoryId() );
717 problemsList = new ArrayList<RepositoryProblemReport>();
718 repositoriesMap.put( repoProblemReport.getRepositoryId(), problemsList );
721 problemsList.add( repoProblemReport );
724 public void setServletRequest( HttpServletRequest request )
726 this.request = request;
729 public List<RepositoryProblemReport> getReports()
734 public String getGroupId()
739 public void setGroupId( String groupId )
741 this.groupId = groupId;
744 public String getRepositoryId()
749 public void setRepositoryId( String repositoryId )
751 this.repositoryId = repositoryId;
754 public String getPrev()
759 public String getNext()
769 public void setPage( int page )
774 public int getRowCount()
779 public void setRowCount( int rowCount )
781 this.rowCount = rowCount;
784 public boolean getIsLastPage()
789 public void setRepositoriesMap( Map<String, List<RepositoryProblemReport>> repositoriesMap )
791 this.repositoriesMap = repositoriesMap;
794 public Map<String, List<RepositoryProblemReport>> getRepositoriesMap()
796 return repositoriesMap;
799 public List<String> getSelectedRepositories()
801 return selectedRepositories;
804 public void setSelectedRepositories( List<String> selectedRepositories )
806 this.selectedRepositories = selectedRepositories;
809 public List<String> getAvailableRepositories()
811 return availableRepositories;
814 public void setAvailableRepositories( List<String> availableRepositories )
816 this.availableRepositories = availableRepositories;
819 public String getStartDate()
824 public void setStartDate( String startDate )
826 this.startDate = startDate;
829 public String getEndDate()
834 public void setEndDate( String endDate )
836 this.endDate = endDate;
839 public List<RepositoryStatistics> getRepositoryStatistics()
841 return repositoryStatistics;
844 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
846 this.repositoryStatistics = repositoryStatistics;
849 public int getReposSize()
854 public void setReposSize( int reposSize )
856 this.reposSize = reposSize;
859 public String getSelectedRepo()
864 public void setSelectedRepo( String selectedRepo )
866 this.selectedRepo = selectedRepo;
869 public DataLimits getLimits()
874 public void setLimits( DataLimits limits )
876 this.limits = limits;
879 public InputStream getInputStream()