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