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