blob: f5b10ab42c39a903d4e71d4805b375a2ba5c9fd7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* George Suaridze <suag@1c.ru> (1C-Soft LLC) - Bug 560168
*******************************************************************************/
package org.eclipse.help.internal.search;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.help.internal.base.remote.RemoteHelp;
import org.eclipse.help.internal.base.remote.RemoteSearchManager;
import org.eclipse.help.internal.search.federated.FederatedSearchEntry;
import org.eclipse.help.internal.search.federated.FederatedSearchJob;
import org.eclipse.help.search.AbstractSearchProcessor;
import org.eclipse.help.search.ISearchResult;
/*
* Manages both local and remote searching, as well as merging of results.
*/
public class SearchManager {
private LocalSearchManager localManager = new LocalSearchManager();
private RemoteSearchManager remoteManager = new RemoteSearchManager();
private class SearchState {
public IProgressMonitor localMonitor;
public IProgressMonitor remoteMonitor;
public ISearchQuery searchQuery;
public BufferedSearchHitCollector bufferedCollector = new BufferedSearchHitCollector();
public Job localSearchJob;
public Job remoteSearchJob;
public SearchState() {
/*
* We use these jobs to perform the local and remote searches in parallel in the
* background.
*/
localSearchJob = new Job("localSearchJob") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
localManager.search(searchQuery, bufferedCollector, localMonitor);
return Status.OK_STATUS;
}
};
remoteSearchJob = new Job("remoteSearchJob") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
remoteManager.search(searchQuery, bufferedCollector, remoteMonitor);
return Status.OK_STATUS;
}
};
localSearchJob.setSystem(true);
remoteSearchJob.setSystem(true);
}
}
/*
* Constructs a new SearchManager.
*/
public SearchManager() {
}
/*
* Perform the given search both locally and remotely if configured.
*/
public void search(ISearchQuery searchQuery, ISearchHitCollector collector, IProgressMonitor pm)
throws QueryTooComplexException {
if (RemoteHelp.isEnabled()) {
searchLocalAndRemote(searchQuery, collector, pm);
}
else {
searchLocal(searchQuery, collector, pm);
}
}
/*
* Perform the given search locally only.
*/
public void searchLocal(ISearchQuery searchQuery, ISearchHitCollector collector, IProgressMonitor pm)
throws QueryTooComplexException {
localManager.search(searchQuery, collector, pm);
}
/*
* Perform the given search both locally and remotely.
*/
public void searchLocalAndRemote(ISearchQuery searchQuery, ISearchHitCollector collector, IProgressMonitor pm)
throws QueryTooComplexException {
SearchState state = new SearchState();
state.searchQuery = searchQuery;
SubMonitor subMonitor = SubMonitor.convert(pm, 100);
// allocate half of the progress bar for each
state.localMonitor = subMonitor.split(50, SubMonitor.SUPPRESS_SUBTASK);
state.remoteMonitor = subMonitor.split(50, SubMonitor.SUPPRESS_SUBTASK);
// start both searches in parallel
state.localSearchJob.schedule();
state.remoteSearchJob.schedule();
// wait until finished
try {
state.localSearchJob.join();
state.remoteSearchJob.join();
}
catch (InterruptedException e) {
String msg = "Unexpected InterruptedException while waiting for help search jobs to finish"; //$NON-NLS-1$
Platform.getLog(getClass()).error(msg, e);
}
// results are in; send them off to the collector
state.bufferedCollector.flush(collector);
pm.done();
}
/**
* Performs the federated search.
*/
public void search(String expression, FederatedSearchEntry[] entries) {
for (int i = 0; i < entries.length; i++) {
FederatedSearchJob job = new FederatedSearchJob(expression, entries[i]);
job.schedule();
}
}
/*
* Returns the manager responsible for handling local searching.
*/
public LocalSearchManager getLocalSearchManager() {
return localManager;
}
/*
* Returns the manager responsible for handling remote searching.
*/
public RemoteSearchManager getRemoteSearchManager() {
return remoteManager;
}
/*
* Performs any necessary cleanup (workbench is shutting down).
*/
public void close() {
localManager.close();
}
/*
* Gets the list of registered search processors
*/
public static AbstractSearchProcessor[] getSearchProcessors()
{
IConfigurationElement[] configs =
Platform.getExtensionRegistry().getConfigurationElementsFor("org.eclipse.help.base.searchProcessor"); //$NON-NLS-1$
ArrayList<Object> processors = new ArrayList<>();
for (int c=0;c<configs.length;c++)
{
try {
processors.add(
configs[c].createExecutableExtension("class"));//$NON-NLS-1$
} catch (CoreException e) {}
}
return processors.toArray(new AbstractSearchProcessor[processors.size()]);
}
/*
* Convert Lucene SearchHits to ISearchResults
*/
public static ISearchResult[] convertHitsToResults(SearchHit hits[]) {
ISearchResult results[] = new ISearchResult[hits.length];
for (int r=0;r<results.length;r++)
{
SearchResult result = new SearchResult();
if (hits[r].getHref()!=null)
result.setHref(hits[r].getHref());
if (hits[r].getId()!=null)
result.setId(hits[r].getId());
if (hits[r].getParticipantId()!=null)
result.setParticipantId(hits[r].getParticipantId());
if (hits[r].getDescription()!=null)
result.setDescription(hits[r].getDescription());
if (hits[r].getLabel()!=null)
result.setLabel(hits[r].getLabel());
if (hits[r].getSummary()!=null)
result.setSummary(hits[r].getSummary());
if (hits[r].getToc()!=null)
result.setToc(hits[r].getToc());
if (hits[r].getIconURL()!=null)
result.setIcon(hits[r].getIconURL());
result.setScore(hits[r].getScore());
result.setPotentialHit(hits[r].isPotentialHit());
results[r] = result;
}
return results;
}
/*
* Convert ISearchResults to SearchHits
*/
public static SearchHit[] convertResultsToHits(ISearchResult[] results) {
SearchHit hits[] = new SearchHit[results.length];
for (int r=0;r<results.length;r++)
{
hits[r] = new SearchHit(
results[r].getHref(),
results[r].getLabel(),
results[r].getSummary(),
results[r].getScore(),
results[r].getToc(),
results[r].getId(),
results[r].getParticipantId(),
results[r].isPotentialHit());
}
return hits;
}
/*
* Buffers hits, and only sends them off to the wrapped collector
* when flush() is called.
*/
private static class BufferedSearchHitCollector implements ISearchHitCollector {
private Set<SearchHit> allHits = new HashSet<>();
private String wordsSearched = null;
@Override
public void addHits(List<SearchHit> hits, String wordsSearched) {
if (wordsSearched != null) {
this.wordsSearched = wordsSearched;
}
allHits.addAll(hits);
}
/*
* Send all the buffered hits to the underlying collector,
* and reset the buffers.
*/
public void flush(ISearchHitCollector collector) {
// sort by score
List<SearchHit> hitsList = new ArrayList<>(allHits);
hitsList.sort(null);
collector.addHits(hitsList, wordsSearched);
allHits.clear();
wordsSearched = null;
}
@Override
public void addQTCException(QueryTooComplexException exception) throws QueryTooComplexException {
throw exception;
}
}
}