]> source.dussan.org Git - archiva.git/blob
49724c86d53234972a31d5e05ee1fad4d6a16687
[archiva.git] /
1 package org.apache.maven.archiva.web.action.reports;
2
3 /*
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
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
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;
32 import java.util.Map;
33 import java.util.TreeMap;
34 import javax.servlet.http.HttpServletRequest;
35
36 import com.opensymphony.xwork2.Preparable;
37 import org.apache.archiva.metadata.repository.stats.RepositoryStatistics;
38 import org.apache.archiva.metadata.repository.stats.RepositoryStatisticsManager;
39 import org.apache.commons.io.IOUtils;
40 import org.apache.commons.lang.StringUtils;
41 import org.apache.commons.lang.time.DateUtils;
42 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
43 import org.apache.maven.archiva.database.ArchivaDAO;
44 import org.apache.maven.archiva.database.Constraint;
45 import org.apache.maven.archiva.database.constraints.RangeConstraint;
46 import org.apache.maven.archiva.database.constraints.RepositoryProblemByGroupIdConstraint;
47 import org.apache.maven.archiva.database.constraints.RepositoryProblemByRepositoryIdConstraint;
48 import org.apache.maven.archiva.database.constraints.RepositoryProblemConstraint;
49 import org.apache.maven.archiva.database.constraints.UniqueFieldConstraint;
50 import org.apache.maven.archiva.model.RepositoryProblem;
51 import org.apache.maven.archiva.model.RepositoryProblemReport;
52 import org.apache.maven.archiva.reporting.ArchivaReportException;
53 import org.apache.maven.archiva.security.ArchivaRoleConstants;
54 import org.apache.maven.archiva.web.action.PlexusActionSupport;
55 import org.apache.struts2.interceptor.ServletRequestAware;
56 import org.codehaus.plexus.redback.rbac.Resource;
57 import org.codehaus.redback.integration.interceptor.SecureAction;
58 import org.codehaus.redback.integration.interceptor.SecureActionBundle;
59 import org.codehaus.redback.integration.interceptor.SecureActionException;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="generateReport" instantiation-strategy="per-lookup"
65  */
66 public class GenerateReportAction
67     extends PlexusActionSupport
68     implements SecureAction, ServletRequestAware, Preparable
69 {
70     public static final String ALL_REPOSITORIES = "All Repositories";
71
72     public static final String BLANK = "blank";
73
74     private static final String[] datePatterns =
75         new String[]{"MM/dd/yy", "MM/dd/yyyy", "MMMMM/dd/yyyy", "MMMMM/dd/yy", "dd MMMMM yyyy", "dd/MM/yy",
76             "dd/MM/yyyy", "yyyy/MM/dd", "yyyy-MM-dd", "yyyy-dd-MM", "MM-dd-yyyy", "MM-dd-yy"};
77
78     public static final String SEND_FILE = "send-file";
79
80     private Logger log = LoggerFactory.getLogger( GenerateReportAction.class );
81
82     /**
83      * @plexus.requirement role-hint="jdo"
84      */
85     private ArchivaDAO dao;
86
87     /**
88      * @plexus.requirement
89      */
90     private ArchivaConfiguration archivaConfiguration;
91
92     /**
93      * @plexus.requirement
94      */
95     private RepositoryStatisticsManager repositoryStatisticsManager;
96
97     private HttpServletRequest request;
98
99     private String groupId;
100
101     private String repositoryId;
102
103     private int page = 1;
104
105     private int rowCount = 100;
106
107     private List<String> selectedRepositories = new ArrayList<String>();
108
109     private String startDate;
110
111     private String endDate;
112
113     private int numPages;
114
115     private Collection<String> repositoryIds;
116
117     private Map<String, List<RepositoryProblemReport>> repositoriesMap =
118         new TreeMap<String, List<RepositoryProblemReport>>();
119
120     private List<String> availableRepositories;
121
122     private List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
123
124     private InputStream inputStream;
125
126     private boolean lastPage;
127
128     @SuppressWarnings("unchecked")
129     public void prepare()
130     {
131         repositoryIds = new ArrayList<String>();
132         repositoryIds.add( ALL_REPOSITORIES ); // comes first to be first in the list
133         repositoryIds.addAll( (List<String>) dao.query(
134             new UniqueFieldConstraint( RepositoryProblem.class.getName(), "repositoryId" ) ) );
135
136         availableRepositories = new ArrayList<String>();
137
138         // remove selected repositories in the option for the statistics report
139         availableRepositories.addAll( archivaConfiguration.getConfiguration().getManagedRepositoriesAsMap().keySet() );
140         for ( String repo : selectedRepositories )
141         {
142             if ( availableRepositories.contains( repo ) )
143             {
144                 availableRepositories.remove( repo );
145             }
146         }
147     }
148
149     /**
150      * Generate the statistics report.
151      *
152      * check whether single repo report or comparison report
153      * 1. if it is a single repository, get all the statistics for the repository on the specified date
154      * - if no date is specified, get only the latest
155      * (total page = 1 --> no pagination since only the most recent stats will be displayed)
156      * - otherwise, get everything within the date range (total pages = repo stats / rows per page)
157      * - required params: repository, startDate, endDate
158      *
159      * 2. if multiple repositories, get the latest statistics on each repository on the specified date
160      * - if no date is specified, use the current date endDate
161      * - required params: repositories, endDate
162      * - total pages = repositories / rows per page
163      *
164      * @return action result
165      */
166     public String generateStatistics()
167     {
168         if ( rowCount < 10 )
169         {
170             // TODO: move to validation framework
171             addFieldError( "rowCount", "Row count must be larger than 10." );
172             return INPUT;
173         }
174         try
175         {
176             Date startDateInDF;
177             Date endDateInDF;
178
179             if ( selectedRepositories.size() > 1 )
180             {
181                 numPages = 1;
182
183                 try
184                 {
185                     startDateInDF = getStartDateInDateFormat();
186                     endDateInDF = getEndDateInDateFormat();
187                 }
188                 catch ( ParseException e )
189                 {
190                     addActionError( "Error parsing date(s)." );
191                     return ERROR;
192                 }
193
194                 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
195                 {
196                     addFieldError( "startDate", "Start Date must be earlier than the End Date" );
197                     return INPUT;
198                 }
199
200                 // multiple repos
201                 for ( String repo : selectedRepositories )
202                 {
203                     List<RepositoryStatistics> stats =
204                         repositoryStatisticsManager.getStatisticsInRange( repo, startDateInDF, endDateInDF );
205                     if ( stats.isEmpty() )
206                     {
207                         log.info( "No statistics available for repository '" + repo + "'." );
208                         // TODO set repo's stats to 0
209                         continue;
210                     }
211
212                     repositoryStatistics.add( stats.get( 0 ) );
213                 }
214             }
215             else if ( selectedRepositories.size() == 1 )
216             {
217                 repositoryId = selectedRepositories.get( 0 );
218                 try
219                 {
220                     startDateInDF = getStartDateInDateFormat();
221                     endDateInDF = getEndDateInDateFormat();
222
223                     if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
224                     {
225                         addFieldError( "startDate", "Start Date must be earlier than the End Date" );
226                         return INPUT;
227                     }
228
229                     List<RepositoryStatistics> stats =
230                         repositoryStatisticsManager.getStatisticsInRange( repositoryId, startDateInDF, endDateInDF );
231
232                     if ( stats.isEmpty() )
233                     {
234                         addActionError(
235                             "No statistics available for repository. Repository might not have been scanned." );
236                         return ERROR;
237                     }
238
239                     int rowCount = getRowCount();
240                     int extraPage = ( stats.size() % rowCount ) != 0 ? 1 : 0;
241                     int totalPages = ( stats.size() / rowCount ) + extraPage;
242                     numPages = totalPages;
243
244                     int currentPage = getPage();
245                     if ( currentPage > totalPages )
246                     {
247                         throw new ArchivaReportException( "The requested page exceeds the total number of pages." );
248                     }
249
250                     int start = rowCount * ( currentPage - 1 );
251                     int end = ( start + rowCount ) - 1;
252
253                     if ( end > stats.size() )
254                     {
255                         end = stats.size() - 1;
256                     }
257
258                     repositoryStatistics = stats.subList( start, end + 1 );
259                 }
260                 catch ( ParseException pe )
261                 {
262                     addActionError( pe.getMessage() );
263                     return ERROR;
264                 }
265             }
266             else
267             {
268                 addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
269                 return INPUT;
270             }
271
272             if ( repositoryStatistics.isEmpty() )
273             {
274                 return BLANK;
275             }
276         }
277         catch ( ArchivaReportException e )
278         {
279             addActionError( "Error encountered while generating report :: " + e.getMessage() );
280             return ERROR;
281         }
282
283         return SUCCESS;
284     }
285
286     /**
287      * Export report to CSV.
288      *
289      * @return action result
290      */
291     public String downloadStatisticsReport()
292     {
293         Date startDateInDF;
294         Date endDateInDF;
295
296         selectedRepositories = parseSelectedRepositories();
297         List<RepositoryStatistics> repositoryStatistics = new ArrayList<RepositoryStatistics>();
298
299         StringBuffer input = null;
300         if ( selectedRepositories.size() > 1 )
301         {
302             try
303             {
304                 startDateInDF = getStartDateInDateFormat();
305                 endDateInDF = getEndDateInDateFormat();
306             }
307             catch ( ParseException e )
308             {
309                 addActionError( "Error parsing date(s)." );
310                 return ERROR;
311             }
312
313             if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
314             {
315                 addFieldError( "startDate", "Start Date must be earlier than the End Date" );
316                 return INPUT;
317             }
318
319             input = new StringBuffer(
320                 "Repository,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
321                     "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
322
323             // multiple repos
324             for ( String repo : selectedRepositories )
325             {
326                 List<RepositoryStatistics> stats =
327                     repositoryStatisticsManager.getStatisticsInRange( repo, startDateInDF, endDateInDF );
328                 if ( stats.isEmpty() )
329                 {
330                     log.info( "No statistics available for repository '" + repo + "'." );
331                     // TODO set repo's stats to 0
332                     continue;
333                 }
334
335                 // only the first one
336                 RepositoryStatistics repositoryStats = stats.get( 0 );
337                 repositoryStatistics.add( repositoryStats );
338
339                 input.append( repo ).append( "," );
340                 input.append( repositoryStats.getTotalFileCount() ).append( "," );
341                 input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
342                 input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
343                 input.append( repositoryStats.getTotalGroupCount() ).append( "," );
344                 input.append( repositoryStats.getTotalProjectCount() );//.append( "," );
345                 // TODO
346 //                input.append( repositoryStats.getPluginCount() ).append( "," );
347 //                input.append( repositoryStats.getArchetypeCount() ).append( "," );
348 //                input.append( repositoryStats.getJarCount() ).append( "," );
349 //                input.append( repositoryStats.getWarCount() ).append( "," );
350 //                input.append( repositoryStats.getDeploymentCount() ).append( "," );
351 //                input.append( repositoryStats.getDownloadCount() ).append( "\n" );
352                 input.append( "\n" );
353             }
354         }
355         else if ( selectedRepositories.size() == 1 )
356         {
357             repositoryId = selectedRepositories.get( 0 );
358             try
359             {
360                 startDateInDF = getStartDateInDateFormat();
361                 endDateInDF = getEndDateInDateFormat();
362
363                 if ( startDateInDF != null && endDateInDF != null && startDateInDF.after( endDateInDF ) )
364                 {
365                     addFieldError( "startDate", "Start Date must be earlier than the End Date" );
366                     return INPUT;
367                 }
368
369                 List<RepositoryStatistics> stats =
370                     repositoryStatisticsManager.getStatisticsInRange( repositoryId, startDateInDF, endDateInDF );
371                 if ( stats.isEmpty() )
372                 {
373                     addActionError( "No statistics available for repository. Repository might not have been scanned." );
374                     return ERROR;
375                 }
376
377                 input = new StringBuffer(
378                     "Date of Scan,Total File Count,Total Size,Artifact Count,Group Count,Project Count," +
379                         "Plugins,Archetypes,Jars,Wars,Deployments,Downloads\n" );
380
381                 for ( RepositoryStatistics repositoryStats : stats )
382                 {
383                     input.append( repositoryStats.getScanStartTime() ).append( "," );
384                     input.append( repositoryStats.getTotalFileCount() ).append( "," );
385                     input.append( repositoryStats.getTotalArtifactFileSize() ).append( "," );
386                     input.append( repositoryStats.getTotalArtifactCount() ).append( "," );
387                     input.append( repositoryStats.getTotalGroupCount() ).append( "," );
388                     input.append( repositoryStats.getTotalProjectCount() );//.append( "," );
389                     // TODO
390 //                input.append( repositoryStats.getPluginCount() ).append( "," );
391 //                input.append( repositoryStats.getArchetypeCount() ).append( "," );
392 //                input.append( repositoryStats.getJarCount() ).append( "," );
393 //                input.append( repositoryStats.getWarCount() ).append( "," );
394 //                input.append( repositoryStats.getDeploymentCount() ).append( "," );
395 //                input.append( repositoryStats.getDownloadCount() );
396                     input.append( "\n" );
397                 }
398
399                 repositoryStatistics = stats;
400             }
401             catch ( ParseException pe )
402             {
403                 addActionError( pe.getMessage() );
404                 return ERROR;
405             }
406         }
407         else
408         {
409             addFieldError( "availableRepositories", "Please select a repository (or repositories) from the list." );
410             return INPUT;
411         }
412
413         if ( repositoryStatistics.isEmpty() )
414         {
415             return BLANK;
416         }
417
418         // write output stream depending on single or comparison report
419         StringReader reader = new StringReader( input.toString() );
420
421         try
422         {
423             inputStream = new ByteArrayInputStream( IOUtils.toByteArray( reader ) );
424         }
425         catch ( IOException i )
426         {
427             addActionError( "Error occurred while generating CSV file." );
428             return ERROR;
429         }
430
431         return SEND_FILE;
432     }
433
434     // hack for parsing the struts list passed as param in <s:url ../>
435     private List<String> parseSelectedRepositories()
436     {
437         List<String> pasedSelectedRepos = new ArrayList<String>();
438
439         for ( String repo : selectedRepositories )
440         {
441             String[] tokens = StringUtils.split( repo, ',' );
442             if ( tokens.length > 1 )
443             {
444                 for ( String token : tokens )
445                 {
446                     pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( token, '[' ), ']' ).trim() );
447                 }
448             }
449             else
450             {
451                 pasedSelectedRepos.add( StringUtils.remove( StringUtils.remove( repo, '[' ), ']' ).trim() );
452             }
453         }
454         return pasedSelectedRepos;
455     }
456
457     private Date getStartDateInDateFormat()
458         throws ParseException
459     {
460         Date startDateInDF;
461         if ( startDate == null || "".equals( startDate ) )
462         {
463             startDateInDF = null;
464         }
465         else
466         {
467             startDateInDF = DateUtils.parseDate( startDate, datePatterns );
468         }
469         return startDateInDF;
470     }
471
472     private Date getEndDateInDateFormat()
473         throws ParseException
474     {
475         Date endDateInDF;
476         if ( endDate == null || "".equals( endDate ) )
477         {
478             endDateInDF = null;
479         }
480         else
481         {
482             endDateInDF = DateUtils.parseDate( endDate, datePatterns );
483
484             // add a day, since we don't inclue time and want the date to be inclusive
485             Calendar cal = Calendar.getInstance();
486             cal.setTime( endDateInDF );
487             cal.add( Calendar.DAY_OF_MONTH, 1 );
488             endDateInDF = cal.getTime();
489         }
490
491         return endDateInDF;
492     }
493
494     public String execute()
495         throws Exception
496     {
497         if ( repositoryId == null )
498         {
499             addFieldError( "repositoryId", "You must provide a repository id." );
500             return INPUT;
501         }
502
503         if ( rowCount < 10 )
504         {
505             addFieldError( "rowCount", "Row count must be larger than 10." );
506             return INPUT;
507         }
508
509         List<RepositoryProblem> problemArtifacts =
510             dao.getRepositoryProblemDAO().queryRepositoryProblems( configureConstraint() );
511
512         String contextPath =
513             request.getRequestURL().substring( 0, request.getRequestURL().indexOf( request.getRequestURI() ) );
514         for ( RepositoryProblem problem : problemArtifacts )
515         {
516             RepositoryProblemReport problemArtifactReport = new RepositoryProblemReport( problem );
517
518             problemArtifactReport.setGroupURL( contextPath + "/browse/" + problem.getGroupId() );
519             problemArtifactReport.setArtifactURL(
520                 contextPath + "/browse/" + problem.getGroupId() + "/" + problem.getArtifactId() );
521
522             List<RepositoryProblemReport> problemsList;
523             if ( repositoriesMap.containsKey( problemArtifactReport.getRepositoryId() ) )
524             {
525                 problemsList = repositoriesMap.get( problemArtifactReport.getRepositoryId() );
526             }
527             else
528             {
529                 problemsList = new ArrayList<RepositoryProblemReport>();
530                 repositoriesMap.put( problemArtifactReport.getRepositoryId(), problemsList );
531             }
532
533             problemsList.add( problemArtifactReport );
534         }
535
536         // TODO: handling should be improved
537         if ( problemArtifacts.size() <= rowCount )
538         {
539             lastPage = true;
540         }
541
542         if ( problemArtifacts.isEmpty() && page == 1 )
543         {
544             return BLANK;
545         }
546         else
547         {
548             return SUCCESS;
549         }
550     }
551
552     private Constraint configureConstraint()
553     {
554         Constraint constraint;
555
556         int[] range =
557             new int[]{( page - 1 ) * rowCount, ( page * rowCount ) + 1}; // Add 1 to check if it's the last page or not.
558
559         if ( groupId != null && ( !groupId.equals( "" ) ) )
560         {
561             if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
562             {
563                 constraint = new RepositoryProblemConstraint( range, groupId, repositoryId );
564             }
565             else
566             {
567                 constraint = new RepositoryProblemByGroupIdConstraint( range, groupId );
568             }
569         }
570         else if ( repositoryId != null && ( !repositoryId.equals( "" ) && !repositoryId.equals( ALL_REPOSITORIES ) ) )
571         {
572             constraint = new RepositoryProblemByRepositoryIdConstraint( range, repositoryId );
573         }
574         else
575         {
576             constraint = new RangeConstraint( range, "repositoryId" );
577         }
578
579         return constraint;
580     }
581
582     public SecureActionBundle getSecureActionBundle()
583         throws SecureActionException
584     {
585         SecureActionBundle bundle = new SecureActionBundle();
586
587         bundle.setRequiresAuthentication( true );
588         bundle.addRequiredAuthorization( ArchivaRoleConstants.OPERATION_ACCESS_REPORT, Resource.GLOBAL );
589
590         return bundle;
591     }
592
593     public Collection<String> getRepositoryIds()
594     {
595         return repositoryIds;
596     }
597
598     public void setServletRequest( HttpServletRequest request )
599     {
600         this.request = request;
601     }
602
603     public String getGroupId()
604     {
605         return groupId;
606     }
607
608     public void setGroupId( String groupId )
609     {
610         this.groupId = groupId;
611     }
612
613     public String getRepositoryId()
614     {
615         return repositoryId;
616     }
617
618     public void setRepositoryId( String repositoryId )
619     {
620         this.repositoryId = repositoryId;
621     }
622
623     public int getPage()
624     {
625         return page;
626     }
627
628     public void setPage( int page )
629     {
630         this.page = page;
631     }
632
633     public int getRowCount()
634     {
635         return rowCount;
636     }
637
638     public void setRowCount( int rowCount )
639     {
640         this.rowCount = rowCount;
641     }
642
643     public void setRepositoriesMap( Map<String, List<RepositoryProblemReport>> repositoriesMap )
644     {
645         this.repositoriesMap = repositoriesMap;
646     }
647
648     public Map<String, List<RepositoryProblemReport>> getRepositoriesMap()
649     {
650         return repositoriesMap;
651     }
652
653     public List<String> getSelectedRepositories()
654     {
655         return selectedRepositories;
656     }
657
658     public void setSelectedRepositories( List<String> selectedRepositories )
659     {
660         this.selectedRepositories = selectedRepositories;
661     }
662
663     public List<String> getAvailableRepositories()
664     {
665         return availableRepositories;
666     }
667
668     public void setAvailableRepositories( List<String> availableRepositories )
669     {
670         this.availableRepositories = availableRepositories;
671     }
672
673     public String getStartDate()
674     {
675         return startDate;
676     }
677
678     public void setStartDate( String startDate )
679     {
680         this.startDate = startDate;
681     }
682
683     public String getEndDate()
684     {
685         return endDate;
686     }
687
688     public void setEndDate( String endDate )
689     {
690         this.endDate = endDate;
691     }
692
693     public List<RepositoryStatistics> getRepositoryStatistics()
694     {
695         return repositoryStatistics;
696     }
697
698     public void setRepositoryStatistics( List<RepositoryStatistics> repositoryStatistics )
699     {
700         this.repositoryStatistics = repositoryStatistics;
701     }
702
703     public boolean isLastPage()
704     {
705         return lastPage;
706     }
707
708     public void setLastPage( boolean lastPage )
709     {
710         this.lastPage = lastPage;
711     }
712
713     public InputStream getInputStream()
714     {
715         return inputStream;
716     }
717
718     public int getNumPages()
719     {
720         return numPages;
721     }
722
723     public void setRepositoryStatisticsManager( RepositoryStatisticsManager repositoryStatisticsManager )
724     {
725         this.repositoryStatisticsManager = repositoryStatisticsManager;
726     }
727 }