]> source.dussan.org Git - archiva.git/blob
493121c908da333cb32510d30910073b4641304f
[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.common.utils.VersionUtil;
38 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
39 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
40 import org.apache.maven.archiva.database.ArchivaDAO;
41 import org.apache.maven.archiva.database.ArtifactDAO;
42 import org.apache.maven.archiva.database.Constraint;
43 import org.apache.maven.archiva.database.constraints.ArtifactsByChecksumConstraint;
44 import org.apache.maven.archiva.database.constraints.UniqueVersionConstraint;
45 import org.apache.maven.archiva.model.ArchivaArtifact;
46 import org.apache.maven.archiva.security.AccessDeniedException;
47 import org.apache.maven.archiva.security.ArchivaSecurityException;
48 import org.apache.maven.archiva.security.ArchivaXworkUser;
49 import org.apache.maven.archiva.security.PrincipalNotFoundException;
50 import org.apache.maven.archiva.security.UserRepositories;
51 import org.apache.struts2.ServletActionContext;
52 import org.springframework.web.context.WebApplicationContext;
53 import org.springframework.web.context.support.WebApplicationContextUtils;
54
55 import com.opensymphony.xwork2.ActionContext;
56 import com.opensymphony.xwork2.Preparable;
57
58 /**
59  * Search all indexed fields by the given criteria.
60  *
61  * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction" instantiation-strategy="per-lookup"
62  */
63 public class SearchAction 
64     extends PlexusActionSupport
65     implements Preparable
66 {
67     /**
68      * Query string.
69      */
70
71     private ArchivaConfiguration archivaConfiguration;
72
73     private String q;
74
75     /**
76      * @plexus.requirement role-hint="jdo"
77      */
78     private ArchivaDAO dao;
79
80     /**
81      * The Search Results.
82      */
83     private SearchResults results;
84     
85     /**
86      * @plexus.requirement
87      */
88     private UserRepositories userRepositories;
89     
90     /**
91      * @plexus.requirement
92      */
93     private ArchivaXworkUser archivaXworkUser;
94     
95     private static final String RESULTS = "results";
96
97     private static final String ARTIFACT = "artifact";
98
99     private List<ArchivaArtifact> databaseResults;
100     
101     private int currentPage = 0;
102     
103     private int totalPages;
104     
105     private boolean searchResultsOnly;
106     
107     private String completeQueryString;
108     
109     private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
110
111     private List<String> managedRepositoryList;
112
113     private String groupId;
114
115     private String artifactId;
116
117     private String version;
118
119     private String className;
120
121     private int rowCount = 30;
122
123     private String repositoryId;
124
125     private boolean fromFilterSearch;
126
127     private boolean filterSearch = false;
128
129     private boolean fromResultsPage;
130
131     private RepositorySearch nexusSearch;
132     
133     private Map<String, String> searchFields;
134         
135     public boolean isFromResultsPage()
136     {
137         return fromResultsPage;
138     }
139
140     public void setFromResultsPage( boolean fromResultsPage )
141     {
142         this.fromResultsPage = fromResultsPage;
143     }
144
145     public boolean isFromFilterSearch()
146     {
147         return fromFilterSearch;
148     }
149
150     public void setFromFilterSearch( boolean fromFilterSearch )
151     {
152         this.fromFilterSearch = fromFilterSearch;
153     }
154
155     public void prepare()
156     {
157         managedRepositoryList = new ArrayList<String>();
158         managedRepositoryList = getObservableRepos();
159
160         if ( managedRepositoryList.size() > 0 )
161         {
162             managedRepositoryList.add( "all" );
163         }
164         
165         searchFields = new LinkedHashMap<String, String>();
166         searchFields.put( "groupId", "Group ID" );
167         searchFields.put( "artifactId", "Artifact ID" );
168         searchFields.put( "version", "Version" );
169         searchFields.put( "className", "Class/Package Name" ); 
170         searchFields.put( "rowCount", "Row Count" );
171         
172         super.clearErrorsAndMessages();       
173         clearSearchFields();
174     }
175     
176     private void clearSearchFields()
177     {
178         repositoryId = "";
179         artifactId = "";
180         groupId = "";
181         version = "";
182         className = "";     
183         rowCount = 30;
184         currentPage = 0;
185     }
186
187     // advanced search MRM-90 -- filtered search
188     public String filteredSearch()
189         throws MalformedURLException
190     {           
191         if ( ( groupId == null || "".equals( groupId ) ) &&
192             ( artifactId == null || "".equals( artifactId ) ) && ( className == null || "".equals( className ) ) &&
193             ( version == null || "".equals( version ) ) )
194         {   
195             addActionError( "Advanced Search - At least one search criteria must be provided." );
196             return INPUT;
197         }
198         
199         fromFilterSearch = true;
200         
201         if ( CollectionUtils.isEmpty( managedRepositoryList ) )
202         {            
203             return GlobalResults.ACCESS_TO_NO_REPOS;
204         }
205
206         SearchResultLimits limits = new SearchResultLimits( currentPage );
207         limits.setPageSize( rowCount );
208         List<String> selectedRepos = new ArrayList<String>();
209         
210         if ( repositoryId == null || StringUtils.isBlank( repositoryId ) ||
211             "all".equals( StringUtils.stripToEmpty( repositoryId ) ) )
212         {
213             selectedRepos = getObservableRepos();
214         }
215         else
216         {
217             selectedRepos.add( repositoryId );
218         }        
219
220         if ( CollectionUtils.isEmpty( selectedRepos ) )
221         {         
222             return GlobalResults.ACCESS_TO_NO_REPOS;
223         }
224
225         SearchFields searchFields =
226             new SearchFields( groupId, artifactId, version, null, className, selectedRepos );
227                 
228         // TODO: add packaging in the list of fields for advanced search (UI)?
229         try
230         {
231             results = getNexusSearch().search( getPrincipal(), searchFields, limits );
232         }
233         catch ( RepositorySearchException e )
234         {
235             addActionError( e.getMessage() );
236             return ERROR;
237         }
238         
239         if ( results.isEmpty() )
240         {
241             addActionError( "No results found" );
242             return INPUT;
243         }
244
245         totalPages = results.getTotalHits() / limits.getPageSize();
246
247         if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
248         {
249             totalPages = totalPages + 1;
250         }
251
252         for (SearchResultHit hit : results.getHits())
253         {
254             final String version = hit.getVersion();
255             if (version != null)
256             {
257                 hit.setVersion(VersionUtil.getBaseVersion(version));
258             }
259         }
260
261         return SUCCESS;
262     }
263
264     @SuppressWarnings("unchecked")
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                 (List<String>) 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     @SuppressWarnings("unchecked")
404     private String getPrincipal()
405     {
406         return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
407     }
408
409     private List<String> getObservableRepos()
410     {
411         try
412         {
413             return userRepositories.getObservableRepositoryIds( getPrincipal() );
414         }
415         catch ( PrincipalNotFoundException e )
416         {
417             log.warn( e.getMessage(), e );
418         }
419         catch ( AccessDeniedException e )
420         {
421             log.warn( e.getMessage(), e );
422         }
423         catch ( ArchivaSecurityException e )
424         {
425             log.warn( e.getMessage(), e );
426         }
427         return Collections.emptyList();
428     }
429
430     private void buildCompleteQueryString( String searchTerm )
431     {
432         if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
433         {
434             searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
435         }
436
437         if ( completeQueryString == null || "".equals( completeQueryString ) )
438         {
439             completeQueryString = searchTerm;
440         }
441         else
442         {
443             completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
444         }
445     }
446
447     private List<String> parseCompleteQueryString()
448     {
449         List<String> parsedCompleteQueryString = new ArrayList<String>();
450         String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
451         CollectionUtils.addAll( parsedCompleteQueryString, parsed );
452
453         return parsedCompleteQueryString;
454     }
455
456     private boolean isEqualToPreviousSearchTerm( String searchTerm )
457     {
458         if ( !"".equals( completeQueryString ) )
459         {
460             String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
461             if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
462             {
463                 return true;
464             }
465         }
466
467         return false;
468     }
469
470     public String getQ()
471     {
472         return q;
473     }
474
475     public void setQ( String q )
476     {
477         this.q = q;
478     }
479
480     public SearchResults getResults()
481     {
482         return results;
483     }
484
485     public List<ArchivaArtifact> getDatabaseResults()
486     {
487         return databaseResults;
488     }
489
490     public void setCurrentPage( int page )
491     {
492         this.currentPage = page;
493     }
494
495     public int getCurrentPage()
496     {
497         return currentPage;
498     }
499
500     public int getTotalPages()
501     {
502         return totalPages;
503     }
504
505     public void setTotalPages( int totalPages )
506     {
507         this.totalPages = totalPages;
508     }
509
510     public boolean isSearchResultsOnly()
511     {
512         return searchResultsOnly;
513     }
514
515     public void setSearchResultsOnly( boolean searchResultsOnly )
516     {
517         this.searchResultsOnly = searchResultsOnly;
518     }
519
520     public String getCompleteQueryString()
521     {
522         return completeQueryString;
523     }
524
525     public void setCompleteQueryString( String completeQueryString )
526     {
527         this.completeQueryString = completeQueryString;
528     }
529
530     public ArchivaConfiguration getArchivaConfiguration()
531     {
532         return archivaConfiguration;
533     }
534
535     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
536     {
537         this.archivaConfiguration = archivaConfiguration;
538     }
539
540     public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
541     {
542         return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
543     }
544
545     public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
546     {
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 }