]> source.dussan.org Git - archiva.git/blob
dab1bf0ebc9daebb6cf3765678bb341d66080326
[archiva.git] /
1 package org.apache.maven.archiva.web.action;
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.net.MalformedURLException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.apache.archiva.indexer.search.RepositorySearch;
30 import org.apache.archiva.indexer.search.RepositorySearchException;
31 import org.apache.archiva.indexer.search.SearchFields;
32 import org.apache.archiva.indexer.search.SearchResultHit;
33 import org.apache.archiva.indexer.search.SearchResultLimits;
34 import org.apache.archiva.indexer.search.SearchResults;
35 import org.apache.commons.collections.CollectionUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
38 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
39 import org.apache.maven.archiva.database.ArchivaDAO;
40 import org.apache.maven.archiva.database.ArtifactDAO;
41 import org.apache.maven.archiva.database.Constraint;
42 import org.apache.maven.archiva.database.constraints.ArtifactsByChecksumConstraint;
43 import org.apache.maven.archiva.security.AccessDeniedException;
44 import org.apache.maven.archiva.security.ArchivaSecurityException;
45 import org.apache.maven.archiva.security.ArchivaXworkUser;
46 import org.apache.maven.archiva.security.PrincipalNotFoundException;
47 import org.apache.maven.archiva.security.UserRepositories;
48
49 import com.opensymphony.xwork2.ActionContext;
50 import com.opensymphony.xwork2.Preparable;
51 import org.apache.maven.archiva.common.utils.VersionUtil;
52 import org.apache.maven.archiva.database.constraints.UniqueVersionConstraint;
53 import org.apache.struts2.ServletActionContext;
54 import org.springframework.web.context.WebApplicationContext;
55 import org.springframework.web.context.support.WebApplicationContextUtils;
56
57 /**
58  * Search all indexed fields by the given criteria.
59  *
60  * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction" instantiation-strategy="per-lookup"
61  */
62 public class SearchAction 
63     extends PlexusActionSupport
64     implements Preparable
65 {
66     /**
67      * Query string.
68      */
69
70     private ArchivaConfiguration archivaConfiguration;
71
72     private Map<String, ManagedRepositoryConfiguration> managedRepositories;
73
74     private String q;
75
76     /**
77      * @plexus.requirement role-hint="jdo"
78      */
79     private ArchivaDAO dao;
80
81     /**
82      * The Search Results.
83      */
84     private SearchResults results;
85     
86     /**
87      * @plexus.requirement
88      */
89     private UserRepositories userRepositories;
90     
91     /**
92      * @plexus.requirement
93      */
94     private ArchivaXworkUser archivaXworkUser;
95     
96     private static final String RESULTS = "results";
97
98     private static final String ARTIFACT = "artifact";
99
100     private List databaseResults;
101     
102     private int currentPage = 0;
103     
104     private int totalPages;
105     
106     private boolean searchResultsOnly;
107     
108     private String completeQueryString;
109     
110     private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
111
112     private List<String> managedRepositoryList;
113
114     private String groupId;
115
116     private String artifactId;
117
118     private String version;
119
120     private String className;
121
122     private int rowCount = 30;
123
124     private String repositoryId;
125
126     private boolean fromFilterSearch;
127
128     private boolean filterSearch = false;
129
130     private boolean fromResultsPage;
131
132     private RepositorySearch nexusSearch;
133     
134     private Map<String, String> searchFields;
135         
136     public boolean isFromResultsPage()
137     {
138         return fromResultsPage;
139     }
140
141     public void setFromResultsPage( boolean fromResultsPage )
142     {
143         this.fromResultsPage = fromResultsPage;
144     }
145
146     public boolean isFromFilterSearch()
147     {
148         return fromFilterSearch;
149     }
150
151     public void setFromFilterSearch( boolean fromFilterSearch )
152     {
153         this.fromFilterSearch = fromFilterSearch;
154     }
155
156     public void prepare()
157     {
158         managedRepositoryList = new ArrayList<String>();
159         managedRepositoryList = getObservableRepos();
160
161         if ( managedRepositoryList.size() > 0 )
162         {
163             managedRepositoryList.add( "all" );
164         }
165         
166         searchFields = new LinkedHashMap<String, String>();
167         searchFields.put( "groupId", "Group ID" );
168         searchFields.put( "artifactId", "Artifact ID" );
169         searchFields.put( "version", "Version" );
170         searchFields.put( "className", "Class/Package Name" ); 
171         searchFields.put( "rowCount", "Row Count" );
172         
173         super.clearErrorsAndMessages();       
174         clearSearchFields();
175     }
176     
177     private void clearSearchFields()
178     {
179         repositoryId = "";
180         artifactId = "";
181         groupId = "";
182         version = "";
183         className = "";     
184         rowCount = 30;
185         currentPage = 0;
186     }
187
188     // advanced search MRM-90 -- filtered search
189     public String filteredSearch()
190         throws MalformedURLException
191     {           
192         if ( ( groupId == null || "".equals( groupId ) ) &&
193             ( artifactId == null || "".equals( artifactId ) ) && ( className == null || "".equals( className ) ) &&
194             ( version == null || "".equals( version ) ) )
195         {   
196             addActionError( "Advanced Search - At least one search criteria must be provided." );
197             return INPUT;
198         }
199         
200         fromFilterSearch = true;
201         
202         if ( CollectionUtils.isEmpty( managedRepositoryList ) )
203         {            
204             return GlobalResults.ACCESS_TO_NO_REPOS;
205         }
206
207         SearchResultLimits limits = new SearchResultLimits( currentPage );
208         limits.setPageSize( rowCount );
209         List<String> selectedRepos = new ArrayList<String>();
210         
211         if ( repositoryId == null || StringUtils.isBlank( repositoryId ) ||
212             "all".equals( StringUtils.stripToEmpty( repositoryId ) ) )
213         {
214             selectedRepos = getObservableRepos();
215         }
216         else
217         {
218             selectedRepos.add( repositoryId );
219         }        
220
221         if ( CollectionUtils.isEmpty( selectedRepos ) )
222         {         
223             return GlobalResults.ACCESS_TO_NO_REPOS;
224         }
225
226         SearchFields searchFields =
227             new SearchFields( groupId, artifactId, version, null, className, selectedRepos );
228                 
229         // TODO: add packaging in the list of fields for advanced search (UI)?
230         try
231         {
232             results = getNexusSearch().search( getPrincipal(), searchFields, limits );
233         }
234         catch ( RepositorySearchException e )
235         {
236             addActionError( e.getMessage() );
237             return ERROR;
238         }
239         
240         if ( results.isEmpty() )
241         {
242             addActionError( "No results found" );
243             return INPUT;
244         }
245
246         totalPages = results.getTotalHits() / limits.getPageSize();
247
248         if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
249         {
250             totalPages = totalPages + 1;
251         }
252
253         for (SearchResultHit hit : results.getHits())
254         {
255             final String version = hit.getVersion();
256             if (version != null)
257             {
258                 hit.setVersion(VersionUtil.getBaseVersion(version));
259             }
260         }
261
262         return SUCCESS;
263     }
264
265     public String quickSearch()
266         throws MalformedURLException
267     {
268         /* TODO: give action message if indexing is in progress.
269          * This should be based off a count of 'unprocessed' artifacts.
270          * This (yet to be written) routine could tell the user that X (unprocessed) artifacts are not yet
271          * present in the full text search.
272          */
273
274         assert q != null && q.length() != 0;
275
276         fromFilterSearch = false;
277
278         SearchResultLimits limits = new SearchResultLimits( currentPage );
279
280         List<String> selectedRepos = getObservableRepos();
281         if ( CollectionUtils.isEmpty( selectedRepos ) )
282         {
283             return GlobalResults.ACCESS_TO_NO_REPOS;
284         }
285
286         try
287         {
288             if( searchResultsOnly && !completeQueryString.equals( "" ) )
289             {                       
290                 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );                   
291             }
292             else
293             {
294                 completeQueryString = "";                    
295                 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, null );                    
296             }
297         }
298         catch ( RepositorySearchException e )
299         {
300             addActionError( e.getMessage() );
301             return ERROR;
302         }
303
304         if ( results.isEmpty() )
305         {
306             addActionError( "No results found" );
307             return INPUT;
308         }
309
310         totalPages = results.getTotalHits() / limits.getPageSize();
311
312         if( (results.getTotalHits() % limits.getPageSize()) != 0 )
313         {
314             totalPages = totalPages + 1;
315         }
316         // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
317
318         /* I don't think that we should, as I expect us to utilize the 'score' system in lucene in
319          * the future to return relevant links better.
320          * I expect the lucene scoring system to take multiple hits on different areas of a single document
321          * to result in a higher score.
322          *   - Joakim
323          */
324
325         if( !isEqualToPreviousSearchTerm( q ) )
326         {
327             buildCompleteQueryString( q );
328         }
329        
330         //Lets get the versions for the artifact we just found and display them
331         //Yes, this is in the lucene index but its more challenging to get them out when we are searching by project
332         
333         // TODO: do we still need to do this? all hits are already filtered in the NexusRepositorySearch
334         //      before being returned as search results
335         for ( SearchResultHit resultHit : results.getHits() )
336         {
337             final List<String> versions =
338                 dao.query( new UniqueVersionConstraint( getObservableRepos(), resultHit.getGroupId(),
339                                                         resultHit.getArtifactId() ) );
340             if ( versions != null && !versions.isEmpty() )
341             {
342                 resultHit.setVersion( null );
343                 resultHit.setVersions( filterTimestampedSnapshots( versions ) );
344             }
345         }
346        
347         return SUCCESS;
348     }
349
350     /**
351      * Remove timestamped snapshots from versions
352      */
353     private static List<String> filterTimestampedSnapshots(List<String> versions)
354     {
355         final List<String> filtered = new ArrayList<String>();
356         for (final String version : versions)
357         {
358             final String baseVersion = VersionUtil.getBaseVersion(version);
359             if (!filtered.contains(baseVersion))
360             {
361                 filtered.add(baseVersion);
362             }
363         }
364         return filtered;
365     }
366
367     public String findArtifact()
368         throws Exception
369     {
370         // TODO: give action message if indexing is in progress
371
372         if ( StringUtils.isBlank( q ) )
373         {
374             addActionError( "Unable to search for a blank checksum" );
375             return INPUT;
376         }
377
378         Constraint constraint = new ArtifactsByChecksumConstraint( q );
379         
380         ArtifactDAO artifactDao = dao.getArtifactDAO();
381         databaseResults = artifactDao.queryArtifacts( constraint );
382
383         if ( databaseResults.isEmpty() )
384         {
385             addActionError( "No results found" );
386             return INPUT;
387         }
388
389         if ( databaseResults.size() == 1 )
390         {
391             // 1 hit? return it's information directly!
392             return ARTIFACT;
393         }
394
395         return RESULTS;
396     }
397     
398     public String doInput()
399     {
400         return INPUT;
401     }
402
403     private String getPrincipal()
404     {
405         return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
406     }
407
408     private List<String> getObservableRepos()
409     {
410         try
411         {
412             return userRepositories.getObservableRepositoryIds( getPrincipal() );
413         }
414         catch ( PrincipalNotFoundException e )
415         {
416             log.warn( e.getMessage(), e );
417         }
418         catch ( AccessDeniedException e )
419         {
420             log.warn( e.getMessage(), e );
421         }
422         catch ( ArchivaSecurityException e )
423         {
424             log.warn( e.getMessage(), e );
425         }
426         return Collections.emptyList();
427     }
428
429     private void buildCompleteQueryString( String searchTerm )
430     {
431         if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
432         {
433             searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
434         }
435
436         if ( completeQueryString == null || "".equals( completeQueryString ) )
437         {
438             completeQueryString = searchTerm;
439         }
440         else
441         {
442             completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
443         }
444     }
445
446     private List<String> parseCompleteQueryString()
447     {
448         List<String> parsedCompleteQueryString = new ArrayList<String>();
449         String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
450         CollectionUtils.addAll( parsedCompleteQueryString, parsed );
451
452         return parsedCompleteQueryString;
453     }
454
455     private boolean isEqualToPreviousSearchTerm( String searchTerm )
456     {
457         if ( !"".equals( completeQueryString ) )
458         {
459             String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
460             if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
461             {
462                 return true;
463             }
464         }
465
466         return false;
467     }
468
469     public String getQ()
470     {
471         return q;
472     }
473
474     public void setQ( String q )
475     {
476         this.q = q;
477     }
478
479     public SearchResults getResults()
480     {
481         return results;
482     }
483
484     public List getDatabaseResults()
485     {
486         return databaseResults;
487     }
488
489     public void setCurrentPage( int page )
490     {
491         this.currentPage = page;
492     }
493
494     public int getCurrentPage()
495     {
496         return currentPage;
497     }
498
499     public int getTotalPages()
500     {
501         return totalPages;
502     }
503
504     public void setTotalPages( int totalPages )
505     {
506         this.totalPages = totalPages;
507     }
508
509     public boolean isSearchResultsOnly()
510     {
511         return searchResultsOnly;
512     }
513
514     public void setSearchResultsOnly( boolean searchResultsOnly )
515     {
516         this.searchResultsOnly = searchResultsOnly;
517     }
518
519     public String getCompleteQueryString()
520     {
521         return completeQueryString;
522     }
523
524     public void setCompleteQueryString( String completeQueryString )
525     {
526         this.completeQueryString = completeQueryString;
527     }
528
529     public ArchivaConfiguration getArchivaConfiguration()
530     {
531         return archivaConfiguration;
532     }
533
534     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
535     {
536         this.archivaConfiguration = archivaConfiguration;
537     }
538
539     public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
540     {
541         return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
542     }
543
544     public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
545     {
546         this.managedRepositories = managedRepositories;
547     }
548
549     public String getGroupId()
550     {
551         return groupId;
552     }
553
554     public void setGroupId( String groupId )
555     {
556         this.groupId = groupId;
557     }
558
559     public String getArtifactId()
560     {
561         return artifactId;
562     }
563
564     public void setArtifactId( String artifactId )
565     {
566         this.artifactId = artifactId;
567     }
568
569     public String getVersion()
570     {
571         return version;
572     }
573
574     public void setVersion( String version )
575     {
576         this.version = version;
577     }
578
579     public int getRowCount()
580     {
581         return rowCount;
582     }
583
584     public void setRowCount( int rowCount )
585     {
586         this.rowCount = rowCount;
587     }
588
589     public boolean isFilterSearch()
590     {
591         return filterSearch;
592     }
593
594     public void setFilterSearch( boolean filterSearch )
595     {
596         this.filterSearch = filterSearch;
597     }
598
599     public String getRepositoryId()
600     {
601         return repositoryId;
602     }
603
604     public void setRepositoryId( String repositoryId )
605     {
606         this.repositoryId = repositoryId;
607     }
608
609     public List<String> getManagedRepositoryList()
610     {
611         return managedRepositoryList;
612     }
613
614     public void setManagedRepositoryList( List<String> managedRepositoryList )
615     {
616         this.managedRepositoryList = managedRepositoryList;
617     }
618
619     public String getClassName()
620     {
621         return className;
622     }
623
624     public void setClassName( String className )
625     {
626         this.className = className;
627     }
628
629     public RepositorySearch getNexusSearch()
630     {
631         // no need to do this when wiring is already in spring
632         if( nexusSearch == null )
633         {
634             WebApplicationContext wac =
635                 WebApplicationContextUtils.getRequiredWebApplicationContext( ServletActionContext.getServletContext() );
636             nexusSearch = ( RepositorySearch ) wac.getBean( "nexusSearch" );
637         }
638         return nexusSearch;
639     }
640
641     public void setNexusSearch( RepositorySearch nexusSearch )
642     {
643         this.nexusSearch = nexusSearch;
644     }
645
646     public ArchivaDAO getDao()
647     {
648         return dao;
649     }
650
651     public void setDao( ArchivaDAO dao )
652     {
653         this.dao = dao;
654     }
655
656     public UserRepositories getUserRepositories()
657     {
658         return userRepositories;
659     }
660
661     public void setUserRepositories( UserRepositories userRepositories )
662     {
663         this.userRepositories = userRepositories;
664     }
665
666     public ArchivaXworkUser getArchivaXworkUser()
667     {
668         return archivaXworkUser;
669     }
670
671     public void setArchivaXworkUser( ArchivaXworkUser archivaXworkUser )
672     {
673         this.archivaXworkUser = archivaXworkUser;
674     }
675
676     public Map<String, String> getSearchFields()
677     {
678         return searchFields;
679     }
680
681     public void setSearchFields( Map<String, String> searchFields )
682     {
683         this.searchFields = searchFields;
684     }
685 }