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 java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.StringReader;
26 import java.text.ParseException;
27 import java.util.ArrayList;
28 import java.util.Calendar;
29 import java.util.Collection;
30 import java.util.Date;
31 import java.util.List;
33 import java.util.TreeMap;
34 import javax.servlet.http.HttpServletRequest;
36 import com.opensymphony.xwork2.Preparable;
37 import org.apache.commons.io.IOUtils;
38 import org.apache.commons.lang.StringUtils;
39 import org.apache.commons.lang.time.DateUtils;
40 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
41 import org.apache.maven.archiva.database.ArchivaDAO;
42 import org.apache.maven.archiva.database.ArchivaDatabaseException;
43 import org.apache.maven.archiva.database.Constraint;
44 import org.apache.maven.archiva.database.ObjectNotFoundException;
45 import org.apache.maven.archiva.database.RepositoryContentStatisticsDAO;
46 import org.apache.maven.archiva.database.constraints.RangeConstraint;
47 import org.apache.maven.archiva.database.constraints.RepositoryContentStatisticsByRepositoryConstraint;
48 import org.apache.maven.archiva.database.constraints.RepositoryProblemByGroupIdConstraint;
49 import org.apache.maven.archiva.database.constraints.RepositoryProblemByRepositoryIdConstraint;
50 import org.apache.maven.archiva.database.constraints.RepositoryProblemConstraint;
51 import org.apache.maven.archiva.database.constraints.UniqueFieldConstraint;
52 import org.apache.maven.archiva.model.RepositoryContentStatistics;
53 import org.apache.maven.archiva.model.RepositoryProblem;
54 import org.apache.maven.archiva.model.RepositoryProblemReport;
55 import org.apache.maven.archiva.reporting.ArchivaReportException;
56 import org.apache.maven.archiva.reporting.DataLimits;
57 import org.apache.maven.archiva.reporting.RepositoryStatistics;
58 import org.apache.maven.archiva.reporting.RepositoryStatisticsReportGenerator;
59 import org.apache.maven.archiva.security.ArchivaRoleConstants;
60 import org.apache.maven.archiva.web.action.PlexusActionSupport;
61 import org.apache.struts2.interceptor.ServletRequestAware;
62 import org.codehaus.plexus.redback.rbac.Resource;
63 import org.codehaus.redback.integration.interceptor.SecureAction;
64 import org.codehaus.redback.integration.interceptor.SecureActionBundle;
65 import org.codehaus.redback.integration.interceptor.SecureActionException;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
70 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="generateReport" instantiation-strategy="per-lookup"
72 public class GenerateReportAction
73 extends PlexusActionSupport
74 implements SecureAction, ServletRequestAware, Preparable
76 private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
79 * @plexus.requirement role-hint="jdo"
81 protected ArchivaDAO dao;
86 private ArchivaConfiguration archivaConfiguration;
88 protected HttpServletRequest request;
90 protected List<RepositoryProblemReport> reports = new ArrayList<RepositoryProblemReport>();
92 protected String groupId;
94 protected String repositoryId;
96 protected String prev;
98 protected String next;
100 protected int[] range = new int[2];
102 protected int page = 1;
104 protected int rowCount = 100;
106 protected boolean isLastPage;
108 public static final String BLANK = "blank";
110 public static final String BASIC = "basic";
112 private static Boolean jasperPresent;
114 private Collection<String> repositoryIds;
116 public static final String ALL_REPOSITORIES = "All Repositories";
118 protected Map<String, List<RepositoryProblemReport>> repositoriesMap =
119 new TreeMap<String, List<RepositoryProblemReport>>();
121 // for statistics report
123 * @plexus.requirement role-hint="simple"
125 private RepositoryStatisticsReportGenerator generator;
127 private List<String> selectedRepositories = new ArrayList<String>();
129 private List<String> availableRepositories;
131 private String startDate;
133 private String endDate;
135 private int reposSize;
137 private String selectedRepo;
139 private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
141 private DataLimits limits = new DataLimits();
143 private String[] datePatterns =
144 new String[]{"MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
145 "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy"};
147 public static final String SEND_FILE = "send-file";
149 private InputStream inputStream;
151 @SuppressWarnings("unchecked")
152 public void prepare()
154 repositoryIds = new ArrayList<String>();
155 repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
156 repositoryIds.addAll( (List<String>) dao.query(
157 new UniqueFieldConstraint( RepositoryProblem.class.getName(), "repositoryId" ) ) );
159 availableRepositories = new ArrayList<String>();
161 // remove selected repositories in the option for the statistics report
162 availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
163 for ( String repo : selectedRepositories )
165 if ( availableRepositories.contains( repo ) )
167 availableRepositories.remove( repo );
172 public Collection<String> getRepositoryIds()
174 return repositoryIds;
178 * Generate the statistics report.
180 * check whether single repo report or comparison report
181 * 1. if it is a single repository, get all the statistics for the repository on the specified date
182 * - if no date is specified, get only the latest
183 * (total page = 1 --> no pagination since only the most recent stats will be displayed)
184 * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
185 * - required params: repository, startDate, endDate
187 * 2. if multiple repositories, get the latest statistics on each repository on the specified date
188 * - if no date is specified, use the current date endDate
189 * - required params: repositories, endDate
190 * - total pages = repositories / rows per page
194 public String generateStatistics()
198 addFieldError( "rowCount", "Row count must be larger than 10." );
201 reposSize = selectedRepositories.size();
205 RepositoryContentStatisticsDAO repoContentStatsDao = dao.getRepositoryContentStatisticsDAO();
206 Date startDateInDF = null;
207 Date endDateInDF = null;
209 if ( selectedRepositories.size() > 1 )
211 limits.setTotalCount( selectedRepositories.size() );
212 limits.setCurrentPage( 1 );
213 limits.setPerPageCount( 1 );
214 limits.setCountOfPages( 1 );
218 startDateInDF = getStartDateInDateFormat();
219 endDateInDF = getEndDateInDateFormat();
221 catch ( ParseException e )
223 addActionError( "Error parsing date(s)." );
227 if ( startDateInDF.after( endDateInDF ) )
229 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
234 generateReportForMultipleRepos( repoContentStatsDao, startDateInDF, endDateInDF, true );
236 else if ( selectedRepositories.size() == 1 )
238 limits.setCurrentPage( getPage() );
239 limits.setPerPageCount( getRowCount() );
241 selectedRepo = selectedRepositories.get( 0 );
244 startDateInDF = getStartDateInDateFormat();
245 endDateInDF = getEndDateInDateFormat();
247 if ( startDateInDF.after( endDateInDF ) )
249 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
253 List<RepositoryContentStatistics> contentStats =
254 repoContentStatsDao.queryRepositoryContentStatistics(
255 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF,
258 if ( contentStats == null || contentStats.isEmpty() )
261 "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 =
271 generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, limits );
273 catch ( ObjectNotFoundException oe )
275 addActionError( oe.getMessage() );
278 catch ( ArchivaDatabaseException de )
280 addActionError( de.getMessage() );
283 catch ( ParseException pe )
285 addActionError( pe.getMessage() );
291 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
295 if ( repositoryStatistics.isEmpty() )
300 catch ( ArchivaReportException e )
302 addActionError( "Error encountered while generating report :: " + e.getMessage() );
310 * Export report to CSV.
314 public String downloadStatisticsReport()
318 Date startDateInDF = null;
319 Date endDateInDF = null;
321 selectedRepositories = parseSelectedRepositories();
322 repositoryStatistics = new ArrayList<RepositoryStatistics>();
324 RepositoryContentStatisticsDAO repoContentStatsDao = dao.getRepositoryContentStatisticsDAO();
325 if ( selectedRepositories.size() > 1 )
329 startDateInDF = getStartDateInDateFormat();
330 endDateInDF = getEndDateInDateFormat();
332 catch ( ParseException e )
334 addActionError( "Error parsing date(s)." );
338 if ( startDateInDF.after( endDateInDF ) )
340 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
345 generateReportForMultipleRepos( repoContentStatsDao, startDateInDF, endDateInDF, false );
347 else if ( selectedRepositories.size() == 1 )
349 selectedRepo = selectedRepositories.get( 0 );
352 startDateInDF = getStartDateInDateFormat();
353 endDateInDF = getEndDateInDateFormat();
355 if ( startDateInDF.after( endDateInDF ) )
357 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
361 List<RepositoryContentStatistics> contentStats =
362 repoContentStatsDao.queryRepositoryContentStatistics(
363 new RepositoryContentStatisticsByRepositoryConstraint( selectedRepo, startDateInDF,
366 if ( contentStats == null || contentStats.isEmpty() )
369 "No statistics available for repository. Repository might not have been scanned." );
373 repositoryStatistics =
374 generator.generateReport( contentStats, selectedRepo, startDateInDF, endDateInDF, false );
376 catch ( ObjectNotFoundException oe )
378 addActionError( oe.getMessage() );
381 catch ( ArchivaDatabaseException de )
383 addActionError( de.getMessage() );
386 catch ( ParseException pe )
388 addActionError( pe.getMessage() );
394 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
398 if ( repositoryStatistics.isEmpty() )
403 catch ( ArchivaReportException e )
405 addActionError( "Error encountered while generating report :: " + e.getMessage() );
409 // write output stream depending on single or comparison report
410 StringBuffer input = getInput();
411 StringReader reader = new StringReader( input.toString() );
415 inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
417 catch ( IOException i )
419 addActionError( "Error occurred while generating CSV file." );
426 // hack for parsing the struts list passed as param in <s:url ../>
427 private List<String> parseSelectedRepositories()
429 List<String> pasedSelectedRepos = new ArrayList<String>();
431 for ( String repo : selectedRepositories )
433 String[] tokens = StringUtils.split( repo, ',' );
434 if ( tokens.length > 1 )
436 for ( int i = 0; i < tokens.length; i++ )
438 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( tokens[i], '[' ), ']' ).trim() );
443 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
446 return pasedSelectedRepos;
449 private void generateReportForMultipleRepos( RepositoryContentStatisticsDAO repoContentStatsDao, Date startDateInDF,
450 Date endDateInDF, boolean useLimits )
451 throws ArchivaReportException
453 for ( String repo : selectedRepositories )
457 List<RepositoryContentStatistics> contentStats = repoContentStatsDao.queryRepositoryContentStatistics(
458 new RepositoryContentStatisticsByRepositoryConstraint( repo, startDateInDF, endDateInDF ) );
460 if ( contentStats == null || contentStats.isEmpty() )
462 log.info( "No statistics available for repository '" + repo + "'." );
463 // TODO set repo's stats to 0
469 repositoryStatistics.addAll(
470 generator.generateReport( contentStats, repo, startDateInDF, endDateInDF, limits ) );
474 repositoryStatistics.addAll(
475 generator.generateReport( contentStats, repo, startDateInDF, endDateInDF, true ) );
478 catch ( ObjectNotFoundException oe )
480 log.error( "No statistics available for repository '" + repo + "'." );
481 // TODO set repo's stats to 0
483 catch ( ArchivaDatabaseException ae )
485 log.error( "Error encountered while querying statistics of repository '" + repo + "'." );
486 // TODO set repo's stats to 0
491 private Date getStartDateInDateFormat()
492 throws ParseException
495 if ( startDate == null || "".equals( startDate ) )
497 startDateInDF = getDefaultStartDate();
501 startDateInDF = DateUtils.parseDate( startDate, datePatterns );
503 return startDateInDF;
506 private Date getEndDateInDateFormat()
507 throws ParseException
510 if ( endDate == null || "".equals( endDate ) )
512 endDateInDF = getDefaultEndDate();
516 endDateInDF = DateUtils.parseDate( endDate, datePatterns );
522 private StringBuffer getInput()
524 StringBuffer input = null;
526 if ( selectedRepositories.size() == 1 )
528 input = new StringBuffer(
529 "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
530 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
532 for ( RepositoryStatistics stats : repositoryStatistics )
534 input.append( stats.getDateOfScan() ).append( "," );
535 input.append( stats.getFileCount() ).append( "," );
536 input.append( stats.getTotalSize() ).append( "," );
537 input.append( stats.getArtifactCount() ).append( "," );
538 input.append( stats.getGroupCount() ).append( "," );
539 input.append( stats.getProjectCount() ).append( "," );
540 input.append( stats.getPluginCount() ).append( "," );
541 input.append( stats.getArchetypeCount() ).append( "," );
542 input.append( stats.getJarCount() ).append( "," );
543 input.append( stats.getWarCount() ).append( "," );
544 input.append( stats.getDeploymentCount() ).append( "," );
545 input.append( stats.getDownloadCount() ).append( "\n" );
548 else if ( selectedRepositories.size() > 1 )
550 input = new StringBuffer(
551 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
552 "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
554 for ( RepositoryStatistics stats : repositoryStatistics )
556 input.append( stats.getRepositoryId() ).append( "," );
557 input.append( stats.getFileCount() ).append( "," );
558 input.append( stats.getTotalSize() ).append( "," );
559 input.append( stats.getArtifactCount() ).append( "," );
560 input.append( stats.getGroupCount() ).append( "," );
561 input.append( stats.getProjectCount() ).append( "," );
562 input.append( stats.getPluginCount() ).append( "," );
563 input.append( stats.getArchetypeCount() ).append( "," );
564 input.append( stats.getJarCount() ).append( "," );
565 input.append( stats.getWarCount() ).append( "," );
566 input.append( stats.getDeploymentCount() ).append( "," );
567 input.append( stats.getDownloadCount() ).append( "\n" );
574 private Date getDefaultStartDate()
576 Calendar cal = Calendar.getInstance();
578 cal.set( 1900, 1, 1, 0, 0, 0 );
580 return cal.getTime();
583 private Date getDefaultEndDate()
585 return Calendar.getInstance().getTime();
588 public String execute()
591 if ( repositoryId == null )
593 addFieldError( "repositoryId", "You must provide a repository id." );
599 addFieldError( "rowCount", "Row count must be larger than 10." );
603 List<RepositoryProblem> problemArtifacts =
604 dao.getRepositoryProblemDAO().queryRepositoryProblems( configureConstraint() );
607 request.getRequestURL().substring( 0, request.getRequestURL().indexOf( request.getRequestURI() ) );
608 RepositoryProblem problemArtifact;
609 RepositoryProblemReport problemArtifactReport;
610 for ( int i = 0; i < problemArtifacts.size(); i++ )
612 problemArtifact = (RepositoryProblem) problemArtifacts.get( i );
613 problemArtifactReport = new RepositoryProblemReport( problemArtifact );
615 problemArtifactReport.setGroupURL( contextPath + "/browse/" + problemArtifact.getGroupId() );
616 problemArtifactReport.setArtifactURL(
617 contextPath + "/browse/" + problemArtifact.getGroupId() + "/" + problemArtifact.getArtifactId() );
619 addToList( problemArtifactReport );
621 // retained the reports list because this is the datasource for the jasper report
622 reports.add( problemArtifactReport );
625 if ( reports.size() <= rowCount )
631 reports.remove( rowCount );
634 prev = request.getRequestURL() + "?page=" + ( page - 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
635 "&repositoryId=" + repositoryId;
636 next = request.getRequestURL() + "?page=" + ( page + 1 ) + "&rowCount=" + rowCount + "&groupId=" + groupId +
637 "&repositoryId=" + repositoryId;
639 if ( reports.size() == 0 && page == 1 )
643 else if ( isJasperPresent() )
653 private static boolean isJasperPresent()
655 if ( jasperPresent == null )
659 Class.forName( "net.sf.jasperreports.engine.JRExporterParameter" );
660 jasperPresent = Boolean.TRUE;
662 catch ( NoClassDefFoundError e )
664 jasperPresent = Boolean.FALSE;
666 catch ( ClassNotFoundException e )
668 jasperPresent = Boolean.FALSE;
671 return jasperPresent.booleanValue();
674 private Constraint configureConstraint()
676 Constraint constraint;
678 range[0] = ( page - 1 ) * rowCount;
679 range[1] = ( page * rowCount ) + 1; // Add 1 to check if it's the last page or not.
681 if ( groupId != null && ( !groupId.equals( "" ) ) )
683 if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
685 constraint = new RepositoryProblemConstraint( range, groupId, repositoryId );
689 constraint = new RepositoryProblemByGroupIdConstraint( range, groupId );
692 else if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
694 constraint = new RepositoryProblemByRepositoryIdConstraint( range, repositoryId );
698 constraint = new RangeConstraint( range, "repositoryId" );
704 public SecureActionBundle getSecureActionBundle()
705 throws SecureActionException
707 SecureActionBundle bundle = new SecureActionBundle();
709 bundle.setRequiresAuthentication( true );
710 bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
715 private void addToList( RepositoryProblemReport repoProblemReport )
717 List<RepositoryProblemReport> problemsList = null;
719 if ( repositoriesMap.containsKey( repoProblemReport.getRepositoryId() ) )
721 problemsList = (List<RepositoryProblemReport>) repositoriesMap.get( repoProblemReport.getRepositoryId() );
725 problemsList = new ArrayList<RepositoryProblemReport>();
726 repositoriesMap.put( repoProblemReport.getRepositoryId(), problemsList );
729 problemsList.add( repoProblemReport );
732 public void setServletRequest( HttpServletRequest request )
734 this.request = request;
737 public List<RepositoryProblemReport> getReports()
742 public String getGroupId()
747 public void setGroupId( String groupId )
749 this.groupId = groupId;
752 public String getRepositoryId()
757 public void setRepositoryId( String repositoryId )
759 this.repositoryId = repositoryId;
762 public String getPrev()
767 public String getNext()
777 public void setPage( int page )
782 public int getRowCount()
787 public void setRowCount( int rowCount )
789 this.rowCount = rowCount;
792 public boolean getIsLastPage()
797 public void setRepositoriesMap( Map<String, List<RepositoryProblemReport>> repositoriesMap )
799 this.repositoriesMap = repositoriesMap;
802 public Map<String, List<RepositoryProblemReport>> getRepositoriesMap()
804 return repositoriesMap;
807 public List<String> getSelectedRepositories()
809 return selectedRepositories;
812 public void setSelectedRepositories( List<String> selectedRepositories )
814 this.selectedRepositories = selectedRepositories;
817 public List<String> getAvailableRepositories()
819 return availableRepositories;
822 public void setAvailableRepositories( List<String> availableRepositories )
824 this.availableRepositories = availableRepositories;
827 public String getStartDate()
832 public void setStartDate( String startDate )
834 this.startDate = startDate;
837 public String getEndDate()
842 public void setEndDate( String endDate )
844 this.endDate = endDate;
847 public List<RepositoryStatistics> getRepositoryStatistics()
849 return repositoryStatistics;
852 public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
854 this.repositoryStatistics = repositoryStatistics;
857 public int getReposSize()
862 public void setReposSize( int reposSize )
864 this.reposSize = reposSize;
867 public String getSelectedRepo()
872 public void setSelectedRepo( String selectedRepo )
874 this.selectedRepo = selectedRepo;
877 public DataLimits getLimits()
882 public void setLimits( DataLimits limits )
884 this.limits = limits;
887 public InputStream getInputStream()