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