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.HashMap;
26 import java.util.List;
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;
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;
60 * Search all indexed fields by the given criteria.
62 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction"
64 public class SearchAction
65 extends PlexusActionSupport
72 private ArchivaConfiguration archivaConfiguration;
74 private Map<String, ManagedRepositoryConfiguration> managedRepositories;
79 * @plexus.requirement role-hint="jdo"
81 private ArchivaDAO dao;
86 private SearchResults results;
91 private UserRepositories userRepositories;
96 private ArchivaXworkUser archivaXworkUser;
98 private static final String RESULTS = "results";
100 private static final String ARTIFACT = "artifact";
102 private List databaseResults;
104 private int currentPage = 0;
106 private int totalPages;
108 private boolean searchResultsOnly;
110 private String completeQueryString;
112 private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
114 private List<String> managedRepositoryList;
116 private String groupId;
118 private String artifactId;
120 private String version;
122 private String className;
124 private int rowCount = 30;
126 private String repositoryId;
128 private boolean fromFilterSearch;
130 private boolean filterSearch = false;
132 private boolean fromResultsPage;
134 private RepositorySearch nexusSearch;
136 private Map<String, String> searchFields;
138 public boolean isFromResultsPage()
140 return fromResultsPage;
143 public void setFromResultsPage( boolean fromResultsPage )
145 this.fromResultsPage = fromResultsPage;
148 public boolean isFromFilterSearch()
150 return fromFilterSearch;
153 public void setFromFilterSearch( boolean fromFilterSearch )
155 this.fromFilterSearch = fromFilterSearch;
158 public void prepare()
160 managedRepositoryList = new ArrayList<String>();
161 managedRepositoryList = getObservableRepos();
163 if ( managedRepositoryList.size() > 0 )
165 managedRepositoryList.add( "all" );
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" );
175 super.clearErrorsAndMessages();
179 private void clearSearchFields()
190 // advanced search MRM-90 -- filtered search
191 public String filteredSearch()
192 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
194 if ( ( groupId == null || "".equals( groupId ) ) &&
195 ( artifactId == null || "".equals( artifactId ) ) && ( className == null || "".equals( className ) ) &&
196 ( version == null || "".equals( version ) ) )
198 addActionError( "Advanced Search - At least one search criteria must be provided." );
202 fromFilterSearch = true;
204 if ( CollectionUtils.isEmpty( managedRepositoryList ) )
206 return GlobalResults.ACCESS_TO_NO_REPOS;
209 SearchResultLimits limits = new SearchResultLimits( currentPage );
210 limits.setPageSize( rowCount );
211 List<String> selectedRepos = new ArrayList<String>();
213 if ( repositoryId == null || StringUtils.isBlank( repositoryId ) ||
214 "all".equals( StringUtils.stripToEmpty( repositoryId ) ) )
216 selectedRepos = getObservableRepos();
220 selectedRepos.add( repositoryId );
223 if ( CollectionUtils.isEmpty( selectedRepos ) )
225 return GlobalResults.ACCESS_TO_NO_REPOS;
228 SearchFields searchFields =
229 new SearchFields( groupId, artifactId, version, null, className, selectedRepos );
231 // TODO: add packaging in the list of fields for advanced search (UI)?
234 results = getNexusSearch().search( getPrincipal(), searchFields, limits );
236 catch ( RepositorySearchException e )
238 addActionError( e.getMessage() );
242 if ( results.isEmpty() )
244 addActionError( "No results found" );
248 totalPages = results.getTotalHits() / limits.getPageSize();
250 if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
252 totalPages = totalPages + 1;
255 for (SearchResultHit hit : results.getHits())
257 final String version = hit.getVersion();
260 hit.setVersion(VersionUtil.getBaseVersion(version));
267 public String quickSearch()
268 throws MalformedURLException, RepositoryIndexException, RepositoryIndexSearchException
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.
276 assert q != null && q.length() != 0;
278 fromFilterSearch = false;
280 SearchResultLimits limits = new SearchResultLimits( currentPage );
282 List<String> selectedRepos = getObservableRepos();
283 if ( CollectionUtils.isEmpty( selectedRepos ) )
285 return GlobalResults.ACCESS_TO_NO_REPOS;
290 if( searchResultsOnly && !completeQueryString.equals( "" ) )
292 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
296 completeQueryString = "";
297 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, null );
300 catch ( RepositorySearchException e )
302 addActionError( e.getMessage() );
306 if ( results.isEmpty() )
308 addActionError( "No results found" );
312 totalPages = results.getTotalHits() / limits.getPageSize();
314 if( (results.getTotalHits() % limits.getPageSize()) != 0 )
316 totalPages = totalPages + 1;
318 // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
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.
327 if( !isEqualToPreviousSearchTerm( q ) )
329 buildCompleteQueryString( q );
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
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() )
339 final List<String> versions =
340 dao.query( new UniqueVersionConstraint( getObservableRepos(), resultHit.getGroupId(),
341 resultHit.getArtifactId() ) );
342 if ( versions != null && !versions.isEmpty() )
344 resultHit.setVersion( null );
345 resultHit.setVersions( filterTimestampedSnapshots( versions ) );
353 * Remove timestamped snapshots from versions
355 private static List<String> filterTimestampedSnapshots(List<String> versions)
357 final List<String> filtered = new ArrayList<String>();
358 for (final String version : versions)
360 final String baseVersion = VersionUtil.getBaseVersion(version);
361 if (!filtered.contains(baseVersion))
363 filtered.add(baseVersion);
369 public String findArtifact()
372 // TODO: give action message if indexing is in progress
374 if ( StringUtils.isBlank( q ) )
376 addActionError( "Unable to search for a blank checksum" );
380 Constraint constraint = new ArtifactsByChecksumConstraint( q );
382 ArtifactDAO artifactDao = dao.getArtifactDAO();
383 databaseResults = artifactDao.queryArtifacts( constraint );
385 if ( databaseResults.isEmpty() )
387 addActionError( "No results found" );
391 if ( databaseResults.size() == 1 )
393 // 1 hit? return it's information directly!
400 public String doInput()
405 private String getPrincipal()
407 return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
410 private List<String> getObservableRepos()
414 return userRepositories.getObservableRepositoryIds( getPrincipal() );
416 catch ( PrincipalNotFoundException e )
418 getLogger().warn( e.getMessage(), e );
420 catch ( AccessDeniedException e )
422 getLogger().warn( e.getMessage(), e );
424 catch ( ArchivaSecurityException e )
426 getLogger().warn( e.getMessage(), e );
428 return Collections.emptyList();
431 private void buildCompleteQueryString( String searchTerm )
433 if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
435 searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
438 if ( completeQueryString == null || "".equals( completeQueryString ) )
440 completeQueryString = searchTerm;
444 completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
448 private List<String> parseCompleteQueryString()
450 List<String> parsedCompleteQueryString = new ArrayList<String>();
451 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
452 CollectionUtils.addAll( parsedCompleteQueryString, parsed );
454 return parsedCompleteQueryString;
457 private boolean isEqualToPreviousSearchTerm( String searchTerm )
459 if ( !"".equals( completeQueryString ) )
461 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
462 if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
476 public void setQ( String q )
481 public SearchResults getResults()
486 public List getDatabaseResults()
488 return databaseResults;
491 public void setCurrentPage( int page )
493 this.currentPage = page;
496 public int getCurrentPage()
501 public int getTotalPages()
506 public void setTotalPages( int totalPages )
508 this.totalPages = totalPages;
511 public boolean isSearchResultsOnly()
513 return searchResultsOnly;
516 public void setSearchResultsOnly( boolean searchResultsOnly )
518 this.searchResultsOnly = searchResultsOnly;
521 public String getCompleteQueryString()
523 return completeQueryString;
526 public void setCompleteQueryString( String completeQueryString )
528 this.completeQueryString = completeQueryString;
531 public ArchivaConfiguration getArchivaConfiguration()
533 return archivaConfiguration;
536 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
538 this.archivaConfiguration = archivaConfiguration;
541 public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
543 return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
546 public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
548 this.managedRepositories = managedRepositories;
551 public String getGroupId()
556 public void setGroupId( String groupId )
558 this.groupId = groupId;
561 public String getArtifactId()
566 public void setArtifactId( String artifactId )
568 this.artifactId = artifactId;
571 public String getVersion()
576 public void setVersion( String version )
578 this.version = version;
581 public int getRowCount()
586 public void setRowCount( int rowCount )
588 this.rowCount = rowCount;
591 public boolean isFilterSearch()
596 public void setFilterSearch( boolean filterSearch )
598 this.filterSearch = filterSearch;
601 public String getRepositoryId()
606 public void setRepositoryId( String repositoryId )
608 this.repositoryId = repositoryId;
611 public List<String> getManagedRepositoryList()
613 return managedRepositoryList;
616 public void setManagedRepositoryList( List<String> managedRepositoryList )
618 this.managedRepositoryList = managedRepositoryList;
621 public String getClassName()
626 public void setClassName( String className )
628 this.className = className;
631 public RepositorySearch getNexusSearch()
633 // no need to do this when wiring is already in spring
634 if( nexusSearch == null )
636 WebApplicationContext wac =
637 WebApplicationContextUtils.getRequiredWebApplicationContext( ServletActionContext.getServletContext() );
638 nexusSearch = ( RepositorySearch ) wac.getBean( "nexusSearch" );
643 public void setNexusSearch( RepositorySearch nexusSearch )
645 this.nexusSearch = nexusSearch;
648 public ArchivaDAO getDao()
653 public void setDao( ArchivaDAO dao )
658 public UserRepositories getUserRepositories()
660 return userRepositories;
663 public void setUserRepositories( UserRepositories userRepositories )
665 this.userRepositories = userRepositories;
668 public ArchivaXworkUser getArchivaXworkUser()
670 return archivaXworkUser;
673 public void setArchivaXworkUser( ArchivaXworkUser archivaXworkUser )
675 this.archivaXworkUser = archivaXworkUser;
678 public Map<String, String> getSearchFields()
683 public void setSearchFields( Map<String, String> searchFields )
685 this.searchFields = searchFields;