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 );
258 if( searchResultsOnly && !completeQueryString.equals( "" ) )
260 results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
264 completeQueryString = "";
265 //results = crossRepoSearch.searchForTerm( getPrincipal(), selectedRepos, q, limits );
268 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, null );
270 catch ( RepositorySearchException e )
272 addActionError( e.getMessage() );
278 if ( results.isEmpty() )
280 addActionError( "No results found" );
284 totalPages = results.getTotalHits() / limits.getPageSize();
286 if( (results.getTotalHits() % limits.getPageSize()) != 0 )
288 totalPages = totalPages + 1;
290 // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
292 /* I don't think that we should, as I expect us to utilize the 'score' system in lucene in
293 * the future to return relevant links better.
294 * I expect the lucene scoring system to take multiple hits on different areas of a single document
295 * to result in a higher score.
299 if( !isEqualToPreviousSearchTerm( q ) )
301 buildCompleteQueryString( q );
304 if (!isbytecodeSearch)
306 //Lets get the versions for the artifact we just found and display them
307 //Yes, this is in the lucene index but its more challenging to get them out when we are searching by project
308 for (SearchResultHit resultHit : results.getHits())
310 final List<String> versions = dao.query(new UniqueVersionConstraint(getObservableRepos(), resultHit.getGroupId(), resultHit.getArtifactId()));
311 if (versions != null && !versions.isEmpty())
313 resultHit.setVersion(null);
314 resultHit.setVersions(filterTimestampedSnapshots(versions));
323 * Remove timestamped snapshots from versions
325 private static List<String> filterTimestampedSnapshots(List<String> versions)
327 final List<String> filtered = new ArrayList<String>();
328 for (final String version : versions)
330 final String baseVersion = VersionUtil.getBaseVersion(version);
331 if (!filtered.contains(baseVersion))
333 filtered.add(baseVersion);
339 public String findArtifact()
342 // TODO: give action message if indexing is in progress
344 if ( StringUtils.isBlank( q ) )
346 addActionError( "Unable to search for a blank checksum" );
350 Constraint constraint = new ArtifactsByChecksumConstraint( q );
351 databaseResults = dao.getArtifactDAO().queryArtifacts( constraint );
353 if ( databaseResults.isEmpty() )
355 addActionError( "No results found" );
359 if ( databaseResults.size() == 1 )
361 // 1 hit? return it's information directly!
368 public String doInput()
373 private String getPrincipal()
375 return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
378 private List<String> getObservableRepos()
382 return userRepositories.getObservableRepositoryIds( getPrincipal() );
384 catch ( PrincipalNotFoundException e )
386 getLogger().warn( e.getMessage(), e );
388 catch ( AccessDeniedException e )
390 getLogger().warn( e.getMessage(), e );
392 catch ( ArchivaSecurityException e )
394 getLogger().warn( e.getMessage(), e );
396 return Collections.emptyList();
399 private void buildCompleteQueryString( String searchTerm )
401 if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
403 searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
406 if ( completeQueryString == null || "".equals( completeQueryString ) )
408 completeQueryString = searchTerm;
412 completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
416 private List<String> parseCompleteQueryString()
418 List<String> parsedCompleteQueryString = new ArrayList<String>();
419 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
420 CollectionUtils.addAll( parsedCompleteQueryString, parsed );
422 return parsedCompleteQueryString;
425 private boolean isEqualToPreviousSearchTerm( String searchTerm )
427 if ( !"".equals( completeQueryString ) )
429 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
430 if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
444 public void setQ( String q )
449 public SearchResults getResults()
454 public List getDatabaseResults()
456 return databaseResults;
459 public void setCurrentPage( int page )
461 this.currentPage = page;
464 public int getCurrentPage()
469 public int getTotalPages()
474 public void setTotalPages( int totalPages )
476 this.totalPages = totalPages;
479 public boolean isSearchResultsOnly()
481 return searchResultsOnly;
484 public void setSearchResultsOnly( boolean searchResultsOnly )
486 this.searchResultsOnly = searchResultsOnly;
489 public String getCompleteQueryString()
491 return completeQueryString;
494 public void setCompleteQueryString( String completeQueryString )
496 this.completeQueryString = completeQueryString;
499 public ArchivaConfiguration getArchivaConfiguration()
501 return archivaConfiguration;
504 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
506 this.archivaConfiguration = archivaConfiguration;
509 public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
511 return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
514 public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
516 this.managedRepositories = managedRepositories;
519 public String getGroupId()
524 public void setGroupId( String groupId )
526 this.groupId = groupId;
529 public String getArtifactId()
534 public void setArtifactId( String artifactId )
536 this.artifactId = artifactId;
539 public String getVersion()
544 public void setVersion( String version )
546 this.version = version;
549 public int getRowCount()
554 public void setRowCount( int rowCount )
556 this.rowCount = rowCount;
559 public boolean isFilterSearch()
564 public void setFilterSearch( boolean filterSearch )
566 this.filterSearch = filterSearch;
569 public String getRepositoryId()
574 public void setRepositoryId( String repositoryId )
576 this.repositoryId = repositoryId;
579 public List<String> getManagedRepositoryList()
581 return managedRepositoryList;
584 public void setManagedRepositoryList( List<String> managedRepositoryList )
586 this.managedRepositoryList = managedRepositoryList;
589 public String getClassName()
594 public void setClassName( String className )
596 this.className = className;
599 public RepositorySearch getNexusSearch()
601 if( nexusSearch == null )
603 WebApplicationContext wac =
604 WebApplicationContextUtils.getRequiredWebApplicationContext( ServletActionContext.getServletContext() );
605 nexusSearch = ( RepositorySearch ) wac.getBean( "nexusSearch" );
610 public void setNexusSearch( RepositorySearch nexusSearch )
612 this.nexusSearch = nexusSearch;