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