1 package org.apache.maven.archiva.web.action;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
22 import java.net.MalformedURLException;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
28 import org.apache.archiva.indexer.util.SearchUtil;
29 import org.apache.commons.collections.CollectionUtils;
30 import org.apache.commons.lang.StringUtils;
31 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
32 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
33 import org.apache.maven.archiva.database.ArchivaDAO;
34 import org.apache.maven.archiva.database.Constraint;
35 import org.apache.maven.archiva.database.constraints.ArtifactsByChecksumConstraint;
36 import org.apache.maven.archiva.indexer.RepositoryIndexException;
37 import org.apache.maven.archiva.indexer.RepositoryIndexSearchException;
38 import org.apache.maven.archiva.indexer.search.CrossRepositorySearch;
39 import org.apache.maven.archiva.indexer.search.SearchResultLimits;
40 import org.apache.maven.archiva.indexer.search.SearchResults;
41 import org.apache.maven.archiva.security.AccessDeniedException;
42 import org.apache.maven.archiva.security.ArchivaSecurityException;
43 import org.apache.maven.archiva.security.ArchivaXworkUser;
44 import org.apache.maven.archiva.security.PrincipalNotFoundException;
45 import org.apache.maven.archiva.security.UserRepositories;
47 import com.opensymphony.xwork2.ActionContext;
48 import com.opensymphony.xwork2.Preparable;
49 import org.apache.maven.archiva.common.utils.VersionUtil;
50 import org.apache.maven.archiva.database.constraints.UniqueVersionConstraint;
51 import org.apache.maven.archiva.indexer.search.SearchResultHit;
54 * Search all indexed fields by the given criteria.
56 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction"
58 public class SearchAction
59 extends PlexusActionSupport
66 private ArchivaConfiguration archivaConfiguration;
68 private Map<String, ManagedRepositoryConfiguration> managedRepositories;
73 * @plexus.requirement role-hint="jdo"
75 private ArchivaDAO dao;
80 private SearchResults results;
83 * @plexus.requirement role-hint="default"
85 private CrossRepositorySearch crossRepoSearch;
90 private UserRepositories userRepositories;
95 private ArchivaXworkUser archivaXworkUser;
97 private static final String RESULTS = "results";
99 private static final String ARTIFACT = "artifact";
101 private List databaseResults;
103 private int currentPage = 0;
105 private int totalPages;
107 private boolean searchResultsOnly;
109 private String completeQueryString;
111 private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
113 private List<String> managedRepositoryList;
115 private String groupId;
117 private String artifactId;
119 private String version;
121 private String className;
123 private int rowCount = 30;
125 private String repositoryId;
127 private boolean fromFilterSearch;
129 private boolean filterSearch = false;
131 private boolean fromResultsPage;
133 public boolean isFromResultsPage()
135 return fromResultsPage;
138 public void setFromResultsPage( boolean fromResultsPage )
140 this.fromResultsPage = fromResultsPage;
143 public boolean isFromFilterSearch()
145 return fromFilterSearch;
148 public void setFromFilterSearch( boolean fromFilterSearch )
150 this.fromFilterSearch = fromFilterSearch;
153 public void prepare()
155 managedRepositoryList = new ArrayList<String>();
156 managedRepositoryList = getObservableRepos();
158 if ( managedRepositoryList.size() > 0 )
160 managedRepositoryList.add( "all" );
164 // advanced search MRM-90 -- filtered search
165 public String filteredSearch()
166 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
168 fromFilterSearch = true;
170 if ( CollectionUtils.isEmpty( managedRepositoryList ) )
172 return GlobalResults.ACCESS_TO_NO_REPOS;
175 SearchResultLimits limits = new SearchResultLimits( currentPage );
177 limits.setPageSize( rowCount );
178 List<String> selectedRepos = new ArrayList<String>();
180 if ( repositoryId.equals( "all" ) )
182 selectedRepos = getObservableRepos();
186 selectedRepos.add( repositoryId );
189 if ( CollectionUtils.isEmpty( selectedRepos ) )
191 return GlobalResults.ACCESS_TO_NO_REPOS;
195 crossRepoSearch.executeFilteredSearch( getPrincipal(), selectedRepos, groupId, artifactId, version,
198 if ( results.isEmpty() )
200 addActionError( "No results found" );
204 totalPages = results.getTotalHits() / limits.getPageSize();
206 if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
208 totalPages = totalPages + 1;
214 public String quickSearch()
215 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
217 /* TODO: give action message if indexing is in progress.
218 * This should be based off a count of 'unprocessed' artifacts.
219 * This (yet to be written) routine could tell the user that X (unprocessed) artifacts are not yet
220 * present in the full text search.
223 assert q != null && q.length() != 0;
225 fromFilterSearch = false;
227 SearchResultLimits limits = new SearchResultLimits( currentPage );
229 List<String> selectedRepos = getObservableRepos();
230 if ( CollectionUtils.isEmpty( selectedRepos ) )
232 return GlobalResults.ACCESS_TO_NO_REPOS;
235 final boolean isbytecodeSearch = SearchUtil.isBytecodeSearch( q );
236 if( isbytecodeSearch )
238 results = crossRepoSearch.searchForBytecode( getPrincipal(), selectedRepos, SearchUtil.removeBytecodeKeyword( q ), limits );
242 if( searchResultsOnly && !completeQueryString.equals( "" ) )
244 results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
248 completeQueryString = "";
249 results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits );
253 if ( results.isEmpty() )
255 addActionError( "No results found" );
259 totalPages = results.getTotalHits() / limits.getPageSize();
261 if( (results.getTotalHits() % limits.getPageSize()) != 0 )
263 totalPages = totalPages + 1;
265 // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
267 /* I don't think that we should, as I expect us to utilize the 'score' system in lucene in
268 * the future to return relevant links better.
269 * I expect the lucene scoring system to take multiple hits on different areas of a single document
270 * to result in a higher score.
274 if( !isEqualToPreviousSearchTerm( q ) )
276 buildCompleteQueryString( q );
279 if (!isbytecodeSearch)
281 //Lets get the versions for the artifact we just found and display them
282 //Yes, this is in the lucene index but its more challenging to get them out when we are searching by project
283 for (SearchResultHit resultHit : results.getHits())
285 final List<String> versions = dao.query(new UniqueVersionConstraint(getObservableRepos(), resultHit.getGroupId(), resultHit.getArtifactId()));
286 if (versions != null && !versions.isEmpty())
288 resultHit.setVersion(null);
289 resultHit.setVersions(filterTimestampedSnapshots(versions));
298 * Remove timestamped snapshots from versions
300 private static List<String> filterTimestampedSnapshots(List<String> versions)
302 final List<String> filtered = new ArrayList<String>();
303 for (final String version : versions)
305 final String baseVersion = VersionUtil.getBaseVersion(version);
306 if (!filtered.contains(baseVersion))
308 filtered.add(baseVersion);
314 public String findArtifact()
317 // TODO: give action message if indexing is in progress
319 if ( StringUtils.isBlank( q ) )
321 addActionError( "Unable to search for a blank checksum" );
325 Constraint constraint = new ArtifactsByChecksumConstraint( q );
326 databaseResults = dao.getArtifactDAO().queryArtifacts( constraint );
328 if ( databaseResults.isEmpty() )
330 addActionError( "No results found" );
334 if ( databaseResults.size() == 1 )
336 // 1 hit? return it's information directly!
343 public String doInput()
348 private String getPrincipal()
350 return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
353 private List<String> getObservableRepos()
357 return userRepositories.getObservableRepositoryIds( getPrincipal() );
359 catch ( PrincipalNotFoundException e )
361 getLogger().warn( e.getMessage(), e );
363 catch ( AccessDeniedException e )
365 getLogger().warn( e.getMessage(), e );
367 catch ( ArchivaSecurityException e )
369 getLogger().warn( e.getMessage(), e );
371 return Collections.emptyList();
374 private void buildCompleteQueryString( String searchTerm )
376 if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
378 searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
381 if ( completeQueryString == null || "".equals( completeQueryString ) )
383 completeQueryString = searchTerm;
387 completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
391 private List<String> parseCompleteQueryString()
393 List<String> parsedCompleteQueryString = new ArrayList<String>();
394 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
395 CollectionUtils.addAll( parsedCompleteQueryString, parsed );
397 return parsedCompleteQueryString;
400 private boolean isEqualToPreviousSearchTerm( String searchTerm )
402 if ( !"".equals( completeQueryString ) )
404 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
405 if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
419 public void setQ( String q )
424 public SearchResults getResults()
429 public List getDatabaseResults()
431 return databaseResults;
434 public void setCurrentPage( int page )
436 this.currentPage = page;
439 public int getCurrentPage()
444 public int getTotalPages()
449 public void setTotalPages( int totalPages )
451 this.totalPages = totalPages;
454 public boolean isSearchResultsOnly()
456 return searchResultsOnly;
459 public void setSearchResultsOnly( boolean searchResultsOnly )
461 this.searchResultsOnly = searchResultsOnly;
464 public String getCompleteQueryString()
466 return completeQueryString;
469 public void setCompleteQueryString( String completeQueryString )
471 this.completeQueryString = completeQueryString;
474 public ArchivaConfiguration getArchivaConfiguration()
476 return archivaConfiguration;
479 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
481 this.archivaConfiguration = archivaConfiguration;
484 public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
486 return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
489 public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
491 this.managedRepositories = managedRepositories;
494 public String getGroupId()
499 public void setGroupId( String groupId )
501 this.groupId = groupId;
504 public String getArtifactId()
509 public void setArtifactId( String artifactId )
511 this.artifactId = artifactId;
514 public String getVersion()
519 public void setVersion( String version )
521 this.version = version;
524 public int getRowCount()
529 public void setRowCount( int rowCount )
531 this.rowCount = rowCount;
534 public boolean isFilterSearch()
539 public void setFilterSearch( boolean filterSearch )
541 this.filterSearch = filterSearch;
544 public String getRepositoryId()
549 public void setRepositoryId( String repositoryId )
551 this.repositoryId = repositoryId;
554 public List<String> getManagedRepositoryList()
556 return managedRepositoryList;
559 public void setManagedRepositoryList( List<String> managedRepositoryList )
561 this.managedRepositoryList = managedRepositoryList;
564 public String getClassName()
569 public void setClassName( String className )
571 this.className = className;