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.LinkedHashMap;
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.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;
55 import com.opensymphony.xwork2.ActionContext;
56 import com.opensymphony.xwork2.Preparable;
59 * Search all indexed fields by the given criteria.
61 * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="searchAction" instantiation-strategy="per-lookup"
63 public class SearchAction
64 extends PlexusActionSupport
71 private ArchivaConfiguration archivaConfiguration;
76 * @plexus.requirement role-hint="jdo"
78 private ArchivaDAO dao;
83 private SearchResults results;
88 private UserRepositories userRepositories;
93 private ArchivaXworkUser archivaXworkUser;
95 private static final String RESULTS = "results";
97 private static final String ARTIFACT = "artifact";
99 private List<ArchivaArtifact> databaseResults;
101 private int currentPage = 0;
103 private int totalPages;
105 private boolean searchResultsOnly;
107 private String completeQueryString;
109 private static final String COMPLETE_QUERY_STRING_SEPARATOR = ";";
111 private List<String> managedRepositoryList;
113 private String groupId;
115 private String artifactId;
117 private String version;
119 private String className;
121 private int rowCount = 30;
123 private String repositoryId;
125 private boolean fromFilterSearch;
127 private boolean filterSearch = false;
129 private boolean fromResultsPage;
131 private RepositorySearch nexusSearch;
133 private Map<String, String> searchFields;
135 public boolean isFromResultsPage()
137 return fromResultsPage;
140 public void setFromResultsPage( boolean fromResultsPage )
142 this.fromResultsPage = fromResultsPage;
145 public boolean isFromFilterSearch()
147 return fromFilterSearch;
150 public void setFromFilterSearch( boolean fromFilterSearch )
152 this.fromFilterSearch = fromFilterSearch;
155 public void prepare()
157 managedRepositoryList = new ArrayList<String>();
158 managedRepositoryList = getObservableRepos();
160 if ( managedRepositoryList.size() > 0 )
162 managedRepositoryList.add( "all" );
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" );
172 super.clearErrorsAndMessages();
176 private void clearSearchFields()
187 // advanced search MRM-90 -- filtered search
188 public String filteredSearch()
189 throws MalformedURLException
191 if ( ( groupId == null || "".equals( groupId ) ) &&
192 ( artifactId == null || "".equals( artifactId ) ) && ( className == null || "".equals( className ) ) &&
193 ( version == null || "".equals( version ) ) )
195 addActionError( "Advanced Search - At least one search criteria must be provided." );
199 fromFilterSearch = true;
201 if ( CollectionUtils.isEmpty( managedRepositoryList ) )
203 return GlobalResults.ACCESS_TO_NO_REPOS;
206 SearchResultLimits limits = new SearchResultLimits( currentPage );
207 limits.setPageSize( rowCount );
208 List<String> selectedRepos = new ArrayList<String>();
210 if ( repositoryId == null || StringUtils.isBlank( repositoryId ) ||
211 "all".equals( StringUtils.stripToEmpty( repositoryId ) ) )
213 selectedRepos = getObservableRepos();
217 selectedRepos.add( repositoryId );
220 if ( CollectionUtils.isEmpty( selectedRepos ) )
222 return GlobalResults.ACCESS_TO_NO_REPOS;
225 SearchFields searchFields =
226 new SearchFields( groupId, artifactId, version, null, className, selectedRepos );
228 // TODO: add packaging in the list of fields for advanced search (UI)?
231 results = getNexusSearch().search( getPrincipal(), searchFields, limits );
233 catch ( RepositorySearchException e )
235 addActionError( e.getMessage() );
239 if ( results.isEmpty() )
241 addActionError( "No results found" );
245 totalPages = results.getTotalHits() / limits.getPageSize();
247 if ( ( results.getTotalHits() % limits.getPageSize() ) != 0 )
249 totalPages = totalPages + 1;
252 for (SearchResultHit hit : results.getHits())
254 final String version = hit.getVersion();
257 hit.setVersion(VersionUtil.getBaseVersion(version));
264 @SuppressWarnings("unchecked")
265 public String quickSearch()
266 throws MalformedURLException
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.
274 assert q != null && q.length() != 0;
276 fromFilterSearch = false;
278 SearchResultLimits limits = new SearchResultLimits( currentPage );
280 List<String> selectedRepos = getObservableRepos();
281 if ( CollectionUtils.isEmpty( selectedRepos ) )
283 return GlobalResults.ACCESS_TO_NO_REPOS;
288 if( searchResultsOnly && !completeQueryString.equals( "" ) )
290 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, parseCompleteQueryString() );
294 completeQueryString = "";
295 results = getNexusSearch().search( getPrincipal(), selectedRepos, q, limits, null );
298 catch ( RepositorySearchException e )
300 addActionError( e.getMessage() );
304 if ( results.isEmpty() )
306 addActionError( "No results found" );
310 totalPages = results.getTotalHits() / limits.getPageSize();
312 if( (results.getTotalHits() % limits.getPageSize()) != 0 )
314 totalPages = totalPages + 1;
316 // TODO: filter / combine the artifacts by version? (is that even possible with non-artifact hits?)
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.
325 if( !isEqualToPreviousSearchTerm( q ) )
327 buildCompleteQueryString( q );
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
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() )
337 final List<String> versions =
338 (List<String>) dao.query( new UniqueVersionConstraint( getObservableRepos(), resultHit.getGroupId(),
339 resultHit.getArtifactId() ) );
340 if ( versions != null && !versions.isEmpty() )
342 resultHit.setVersion( null );
343 resultHit.setVersions( filterTimestampedSnapshots( versions ) );
351 * Remove timestamped snapshots from versions
353 private static List<String> filterTimestampedSnapshots(List<String> versions)
355 final List<String> filtered = new ArrayList<String>();
356 for (final String version : versions)
358 final String baseVersion = VersionUtil.getBaseVersion(version);
359 if (!filtered.contains(baseVersion))
361 filtered.add(baseVersion);
367 public String findArtifact()
370 // TODO: give action message if indexing is in progress
372 if ( StringUtils.isBlank( q ) )
374 addActionError( "Unable to search for a blank checksum" );
378 Constraint constraint = new ArtifactsByChecksumConstraint( q );
380 ArtifactDAO artifactDao = dao.getArtifactDAO();
381 databaseResults = artifactDao.queryArtifacts( constraint );
383 if ( databaseResults.isEmpty() )
385 addActionError( "No results found" );
389 if ( databaseResults.size() == 1 )
391 // 1 hit? return it's information directly!
398 public String doInput()
403 @SuppressWarnings("unchecked")
404 private String getPrincipal()
406 return archivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() );
409 private List<String> getObservableRepos()
413 return userRepositories.getObservableRepositoryIds( getPrincipal() );
415 catch ( PrincipalNotFoundException e )
417 log.warn( e.getMessage(), e );
419 catch ( AccessDeniedException e )
421 log.warn( e.getMessage(), e );
423 catch ( ArchivaSecurityException e )
425 log.warn( e.getMessage(), e );
427 return Collections.emptyList();
430 private void buildCompleteQueryString( String searchTerm )
432 if ( searchTerm.indexOf( COMPLETE_QUERY_STRING_SEPARATOR ) != -1 )
434 searchTerm = StringUtils.remove( searchTerm, COMPLETE_QUERY_STRING_SEPARATOR );
437 if ( completeQueryString == null || "".equals( completeQueryString ) )
439 completeQueryString = searchTerm;
443 completeQueryString = completeQueryString + COMPLETE_QUERY_STRING_SEPARATOR + searchTerm;
447 private List<String> parseCompleteQueryString()
449 List<String> parsedCompleteQueryString = new ArrayList<String>();
450 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
451 CollectionUtils.addAll( parsedCompleteQueryString, parsed );
453 return parsedCompleteQueryString;
456 private boolean isEqualToPreviousSearchTerm( String searchTerm )
458 if ( !"".equals( completeQueryString ) )
460 String[] parsed = StringUtils.split( completeQueryString, COMPLETE_QUERY_STRING_SEPARATOR );
461 if ( StringUtils.equalsIgnoreCase( searchTerm, parsed[parsed.length - 1] ) )
475 public void setQ( String q )
480 public SearchResults getResults()
485 public List<ArchivaArtifact> getDatabaseResults()
487 return databaseResults;
490 public void setCurrentPage( int page )
492 this.currentPage = page;
495 public int getCurrentPage()
500 public int getTotalPages()
505 public void setTotalPages( int totalPages )
507 this.totalPages = totalPages;
510 public boolean isSearchResultsOnly()
512 return searchResultsOnly;
515 public void setSearchResultsOnly( boolean searchResultsOnly )
517 this.searchResultsOnly = searchResultsOnly;
520 public String getCompleteQueryString()
522 return completeQueryString;
525 public void setCompleteQueryString( String completeQueryString )
527 this.completeQueryString = completeQueryString;
530 public ArchivaConfiguration getArchivaConfiguration()
532 return archivaConfiguration;
535 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
537 this.archivaConfiguration = archivaConfiguration;
540 public Map<String, ManagedRepositoryConfiguration> getManagedRepositories()
542 return getArchivaConfiguration().getConfiguration().getManagedRepositoriesAsMap();
545 public void setManagedRepositories( Map<String, ManagedRepositoryConfiguration> managedRepositories )
549 public String getGroupId()
554 public void setGroupId( String groupId )
556 this.groupId = groupId;
559 public String getArtifactId()
564 public void setArtifactId( String artifactId )
566 this.artifactId = artifactId;
569 public String getVersion()
574 public void setVersion( String version )
576 this.version = version;
579 public int getRowCount()
584 public void setRowCount( int rowCount )
586 this.rowCount = rowCount;
589 public boolean isFilterSearch()
594 public void setFilterSearch( boolean filterSearch )
596 this.filterSearch = filterSearch;
599 public String getRepositoryId()
604 public void setRepositoryId( String repositoryId )
606 this.repositoryId = repositoryId;
609 public List<String> getManagedRepositoryList()
611 return managedRepositoryList;
614 public void setManagedRepositoryList( List<String> managedRepositoryList )
616 this.managedRepositoryList = managedRepositoryList;
619 public String getClassName()
624 public void setClassName( String className )
626 this.className = className;
629 public RepositorySearch getNexusSearch()
631 // no need to do this when wiring is already in spring
632 if( nexusSearch == null )
634 WebApplicationContext wac =
635 WebApplicationContextUtils.getRequiredWebApplicationContext( ServletActionContext.getServletContext() );
636 nexusSearch = ( RepositorySearch ) wac.getBean( "nexusSearch" );
641 public void setNexusSearch( RepositorySearch nexusSearch )
643 this.nexusSearch = nexusSearch;
646 public ArchivaDAO getDao()
651 public void setDao( ArchivaDAO dao )
656 public UserRepositories getUserRepositories()
658 return userRepositories;
661 public void setUserRepositories( UserRepositories userRepositories )
663 this.userRepositories = userRepositories;
666 public ArchivaXworkUser getArchivaXworkUser()
668 return archivaXworkUser;
671 public void setArchivaXworkUser( ArchivaXworkUser archivaXworkUser )
673 this.archivaXworkUser = archivaXworkUser;
676 public Map<String, String> getSearchFields()
681 public void setSearchFields( Map<String, String> searchFields )
683 this.searchFields = searchFields;