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.search.RepositorySearch;
29 import org.apache.archiva.indexer.search.RepositorySearchException;
30 import org.apache.archiva.indexer.util.SearchUtil;
31 import org.apache.commons.collections.CollectionUtils;
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
34 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
35 import org.apache.maven.archiva.database.ArchivaDAO;
36 import org.apache.maven.archiva.database.Constraint;
37 import org.apache.maven.archiva.database.constraints.ArtifactsByChecksumConstraint;
38 import org.apache.maven.archiva.indexer.RepositoryIndexException;
39 import org.apache.maven.archiva.indexer.RepositoryIndexSearchException;
40 import org.apache.maven.archiva.indexer.search.CrossRepositorySearch;
41 import org.apache.maven.archiva.indexer.search.SearchResultLimits;
42 import org.apache.maven.archiva.indexer.search.SearchResults;
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;
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.maven.archiva.indexer.search.SearchResultHit;
54 import org.apache.struts2.ServletActionContext;
55 import org.springframework.web.context.WebApplicationContext;
56 import org.springframework.web.context.support.WebApplicationContextUtils;
59 * Search all indexed fields by the given criteria.
61 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction"
63 public class SearchAction
64 extends PlexusActionSupport
71 private ArchivaConfiguration archivaConfiguration;
73 private Map<String, ManagedRepositoryConfiguration> managedRepositories;
78 * @plexus.requirement role-hint="jdo"
80 private ArchivaDAO dao;
85 private SearchResults results;
88 * @plexus.requirement role-hint="default"
90 private CrossRepositorySearch crossRepoSearch;
95 private UserRepositories userRepositories;
100 private ArchivaXworkUser archivaXworkUser;
102 private static final String RESULTS = "results";
104 private static final String ARTIFACT = "artifact";
106 private List databaseResults;
108 private int currentPage = 0;
110 private int totalPages;
112 private boolean searchResultsOnly;
114 private String completeQueryString;
116 private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
118 private List<String> managedRepositoryList;
120 private String groupId;
122 private String artifactId;
124 private String version;
126 private String className;
128 private int rowCount = 30;
130 private String repositoryId;
132 private boolean fromFilterSearch;
134 private boolean filterSearch = false;
136 private boolean fromResultsPage;
138 private RepositorySearch nexusSearch;
140 public boolean isFromResultsPage()
142 return fromResultsPage;
145 public void setFromResultsPage( boolean fromResultsPage )
147 this.fromResultsPage = fromResultsPage;
150 public boolean isFromFilterSearch()
152 return fromFilterSearch;
155 public void setFromFilterSearch( boolean fromFilterSearch )
157 this.fromFilterSearch = fromFilterSearch;
160 public void prepare()
162 managedRepositoryList = new ArrayList<String>();
163 managedRepositoryList = getObservableRepos();
165 if ( managedRepositoryList.size() > 0 )
167 managedRepositoryList.add( "all" );
171 // advanced search MRM-90 -- filtered search
172 public String filteredSearch()
173 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
175 fromFilterSearch = true;
177 if ( CollectionUtils.isEmpty( managedRepositoryList ) )
179 return GlobalResults.ACCESS_TO_NO_REPOS;
182 SearchResultLimits limits = new SearchResultLimits( currentPage );
184 limits.setPageSize( rowCount );
185 List<String> selectedRepos = new ArrayList<String>();
187 if ( repositoryId.equals( "all" ) )
189 selectedRepos = getObservableRepos();
193 selectedRepos.add( repositoryId );
196 if ( CollectionUtils.isEmpty( selectedRepos ) )
198 return GlobalResults.ACCESS_TO_NO_REPOS;
202 crossRepoSearch.executeFilteredSearch( getPrincipal(), selectedRepos, groupId, artifactId, version,
205 if ( results.isEmpty() )
207 addActionError( "No results found" );
211 totalPages = results.getTotalHits() / limits.getPageSize();
213 if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
215 totalPages = totalPages + 1;
218 for (SearchResultHit hit : results.getHits())
220 final String version = hit.getVersion();
223 hit.setVersion(VersionUtil.getBaseVersion(version));
230 public String quickSearch()
231 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
233 /* TODO: give action message if indexing is in progress.
234 * This should be based off a count of 'unprocessed' artifacts.
235 * This (yet to be written) routine could tell the user that X (unprocessed) artifacts are not yet
236 * present in the full text search.
239 assert q != null && q.length() != 0;
241 fromFilterSearch = false;
243 SearchResultLimits limits = new SearchResultLimits( currentPage );
245 List<String> selectedRepos = getObservableRepos();
246 if ( CollectionUtils.isEmpty( selectedRepos ) )
248 return GlobalResults.ACCESS_TO_NO_REPOS;
251 final boolean isbytecodeSearch = SearchUtil.isBytecodeSearch( q );
252 if( isbytecodeSearch )
254 results = crossRepoSearch.searchForBytecode( getPrincipal(), selectedRepos, SearchUtil.removeBytecodeKeyword( q ), limits );
260 if( searchResultsOnly && !completeQueryString.equals( "" ) )
262 //results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
263 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
267 completeQueryString = "";
268 //results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits );
269 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, null );
272 catch ( RepositorySearchException e )
274 addActionError( e.getMessage() );
279 if ( results.isEmpty() )
281 addActionError( "No results found" );
285 totalPages = results.getTotalHits() / limits.getPageSize();
287 if( (results.getTotalHits() % limits.getPageSize()) != 0 )
289 totalPages = totalPages + 1;
291 // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
293 /* I don't think that we should, as I expect us to utilize the 'score' system in lucene in
294 * the future to return relevant links better.
295 * I expect the lucene scoring system to take multiple hits on different areas of a single document
296 * to result in a higher score.
300 if( !isEqualToPreviousSearchTerm( q ) )
302 buildCompleteQueryString( q );
305 if (!isbytecodeSearch)
307 //Lets get the versions for the artifact we just found and display them
308 //Yes, this is in the lucene index but its more challenging to get them out when we are searching by project
309 for (SearchResultHit resultHit : results.getHits())
311 final List<String> versions = dao.query(new UniqueVersionConstraint(getObservableRepos(), resultHit.getGroupId(), resultHit.getArtifactId()));
312 if (versions != null && !versions.isEmpty())
314 resultHit.setVersion(null);
315 resultHit.setVersions(filterTimestampedSnapshots(versions));
324 * Remove timestamped snapshots from versions
326 private static List<String> filterTimestampedSnapshots(List<String> versions)
328 final List<String> filtered = new ArrayList<String>();
329 for (final String version : versions)
331 final String baseVersion = VersionUtil.getBaseVersion(version);
332 if (!filtered.contains(baseVersion))
334 filtered.add(baseVersion);
340 public String findArtifact()
343 // TODO: give action message if indexing is in progress
345 if ( StringUtils.isBlank( q ) )
347 addActionError( "Unable to search for a blank checksum" );
351 Constraint constraint = new ArtifactsByChecksumConstraint( q );
352 databaseResults = dao.getArtifactDAO().queryArtifacts( constraint );
354 if ( databaseResults.isEmpty() )
356 addActionError( "No results found" );
360 if ( databaseResults.size() == 1 )
362 // 1 hit? return it's information directly!
369 public String doInput()
374 private String getPrincipal()
376 return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
379 private List<String> getObservableRepos()
383 return userRepositories.getObservableRepositoryIds( getPrincipal() );
385 catch ( PrincipalNotFoundException e )
387 getLogger().warn( e.getMessage(), e );
389 catch ( AccessDeniedException e )
391 getLogger().warn( e.getMessage(), e );
393 catch ( ArchivaSecurityException e )
395 getLogger().warn( e.getMessage(), e );
397 return Collections.emptyList();
400 private void buildCompleteQueryString( String searchTerm )
402 if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
404 searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
407 if ( completeQueryString == null || "".equals( completeQueryString ) )
409 completeQueryString = searchTerm;
413 completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
417 private List<String> parseCompleteQueryString()
419 List<String> parsedCompleteQueryString = new ArrayList<String>();
420 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
421 CollectionUtils.addAll( parsedCompleteQueryString, parsed );
423 return parsedCompleteQueryString;
426 private boolean isEqualToPreviousSearchTerm( String searchTerm )
428 if ( !"".equals( completeQueryString ) )
430 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
431 if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
445 public void setQ( String q )
450 public SearchResults getResults()
455 public List getDatabaseResults()
457 return databaseResults;
460 public void setCurrentPage( int page )
462 this.currentPage = page;
465 public int getCurrentPage()
470 public int getTotalPages()
475 public void setTotalPages( int totalPages )
477 this.totalPages = totalPages;
480 public boolean isSearchResultsOnly()
482 return searchResultsOnly;
485 public void setSearchResultsOnly( boolean searchResultsOnly )
487 this.searchResultsOnly = searchResultsOnly;
490 public String getCompleteQueryString()
492 return completeQueryString;
495 public void setCompleteQueryString( String completeQueryString )
497 this.completeQueryString = completeQueryString;
500 public ArchivaConfiguration getArchivaConfiguration()
502 return archivaConfiguration;
505 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
507 this.archivaConfiguration = archivaConfiguration;
510 public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
512 return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
515 public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
517 this.managedRepositories = managedRepositories;
520 public String getGroupId()
525 public void setGroupId( String groupId )
527 this.groupId = groupId;
530 public String getArtifactId()
535 public void setArtifactId( String artifactId )
537 this.artifactId = artifactId;
540 public String getVersion()
545 public void setVersion( String version )
547 this.version = version;
550 public int getRowCount()
555 public void setRowCount( int rowCount )
557 this.rowCount = rowCount;
560 public boolean isFilterSearch()
565 public void setFilterSearch( boolean filterSearch )
567 this.filterSearch = filterSearch;
570 public String getRepositoryId()
575 public void setRepositoryId( String repositoryId )
577 this.repositoryId = repositoryId;
580 public List<String> getManagedRepositoryList()
582 return managedRepositoryList;
585 public void setManagedRepositoryList( List<String> managedRepositoryList )
587 this.managedRepositoryList = managedRepositoryList;
590 public String getClassName()
595 public void setClassName( String className )
597 this.className = className;
600 public RepositorySearch getNexusSearch()
602 if( nexusSearch == null )
604 WebApplicationContext wac =
605 WebApplicationContextUtils.getRequiredWebApplicationContext( ServletActionContext.getServletContext() );
606 nexusSearch = ( RepositorySearch ) wac.getBean( "nexusSearch" );
611 public void setNexusSearch( RepositorySearch nexusSearch )
613 this.nexusSearch = nexusSearch;