blob: 66e4f93cd3701685896ac245cf9f0924fb787f02 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 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
*******************************************************************************/
package org.eclipse.help.internal.search;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Hashtable;
import javax.xml.parsers.*;
import org.eclipse.core.runtime.*;
import org.eclipse.help.IHelpResource;
import org.eclipse.help.internal.base.*;
import org.eclipse.help.internal.base.util.ProxyUtil;
import org.eclipse.help.internal.entityresolver.LocalEntityResolver;
import org.eclipse.help.search.ISearchEngine;
import org.eclipse.help.search.ISearchEngineResult;
import org.eclipse.help.search.ISearchEngineResultCollector;
import org.eclipse.help.search.ISearchScope;
import org.w3c.dom.*;
import org.xml.sax.*;
/**
* This implementation of <code>ISearchEngine</code> interface performs search
* by running a query on the remote InfoCenter and presenting the results
* locally. Instances of this engine type are required to supply the URL to the
* InfoCenter.
*
* <p>
* This class is made public in order to be instantiated and parametrized
* directly in the extentsions. Clients are required to supply the required URL
* as a parameter <code>url</code>.
*
* <p>
* This class is not expected to be subclassed or otherwise accessed
* programmatically.
*
* @since 3.1
*/
public final class InfoCenter implements ISearchEngine {
private Hashtable<String, IHelpResource> tocs;
public static class Scope implements ISearchScope {
String url;
boolean searchSelected;
String[] tocs;
public Scope(String url, boolean searchSelected, String[] tocs) {
this.url = url;
this.searchSelected = searchSelected;
this.tocs = tocs;
}
}
private class InfoCenterResult implements ISearchEngineResult {
private IHelpResource category;
private Element node;
private String baseURL;
public InfoCenterResult(String baseURL, Element node) {
this.baseURL = baseURL;
this.node = node;
createCategory(node);
}
private void createCategory(Element node) {
final String href = node.getAttribute("toc"); //$NON-NLS-1$
final String label = node.getAttribute("toclabel"); //$NON-NLS-1$
if (href != null && label != null) {
category = tocs.get(href);
if (category == null) {
category = new IHelpResource() {
@Override
public String getLabel() {
return label;
}
@Override
public String getHref() {
return href;
}
};
tocs.put(href, category);
}
}
}
@Override
public String getLabel() {
return node.getAttribute("label"); //$NON-NLS-1$
}
@Override
public String getDescription() {
return null;
}
@Override
public IHelpResource getCategory() {
return category;
}
@Override
public String getHref() {
return node.getAttribute("href"); //$NON-NLS-1$
}
@Override
public float getScore() {
String value = node.getAttribute("score"); //$NON-NLS-1$
if (value != null)
return Float.parseFloat(value);
return (float) 0.0;
}
@Override
public boolean getForceExternalWindow() {
return false;
}
@Override
public String toAbsoluteHref(String href, boolean frames) {
String url = baseURL;
if (!url.endsWith("/")) //$NON-NLS-1$
url = url + "/"; //$NON-NLS-1$
if (frames) {
return url + "topic" + href; //$NON-NLS-1$
}
if (url.indexOf('?')> 0) {
return url + "topic" + href + "&noframes=true"; //$NON-NLS-1$ //$NON-NLS-2$
}
return url + "topic" + href + "?noframes=true"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* The default constructor.
*/
public InfoCenter() {
tocs = new Hashtable<>();
}
@Override
public void run(String query, ISearchScope scope,
ISearchEngineResultCollector collector, IProgressMonitor monitor)
throws CoreException {
URL url = createURL(query, (Scope) scope);
if (url == null)
return;
InputStream is = null;
tocs.clear();
try {
URLConnection connection = ProxyUtil.getConnection(url);
SubMonitor subMonitor = SubMonitor.convert(monitor, HelpBaseResources.InfoCenter_connecting, 5);
is = connection.getInputStream();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
subMonitor.worked(1);
load(((Scope) scope).url, reader, collector, subMonitor.split(4));
}
} catch (FileNotFoundException e) {
reportError(HelpBaseResources.InfoCenter_fileNotFound, e, collector);
} catch (IOException e) {
reportError(HelpBaseResources.InfoCenter_io, e, collector);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
private void reportError(String message, IOException e,
ISearchEngineResultCollector collector) {
Status status = new Status(IStatus.ERROR, HelpBasePlugin.PLUGIN_ID,
IStatus.OK, message, e);
collector.error(status);
}
private void load(String baseURL, Reader r,
ISearchEngineResultCollector collector, IProgressMonitor monitor) {
Document document = null;
try {
DocumentBuilder parser = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
parser.setEntityResolver(new LocalEntityResolver());
if (monitor.isCanceled())
return;
SubMonitor subMonitor = SubMonitor.convert(monitor, 5);
subMonitor.subTask(HelpBaseResources.InfoCenter_searching);
document = parser.parse(new InputSource(r));
if (monitor.isCanceled())
return;
// Strip out any comments first
Node root = document.getFirstChild();
while (root.getNodeType() == Node.COMMENT_NODE) {
document.removeChild(root);
root = document.getFirstChild();
if (monitor.isCanceled())
return;
}
subMonitor.worked(1);
load(baseURL, document, (Element) root, collector, subMonitor.split(4));
} catch (ParserConfigurationException e) {
// ignore
} catch (IOException e) {
// ignore
} catch (SAXException e) {
// ignore
}
}
private void load(String baseURL, Document doc, Element root,
ISearchEngineResultCollector collector, IProgressMonitor monitor) {
NodeList topics = root.getElementsByTagName("hit"); //$NON-NLS-1$
ISearchEngineResult[] results = new ISearchEngineResult[topics
.getLength()];
monitor.subTask(HelpBaseResources.InfoCenter_processing);
monitor.beginTask("", results.length); //$NON-NLS-1$
for (int i = 0; i < topics.getLength(); i++) {
Element el = (Element) topics.item(i);
if (monitor.isCanceled())
break;
results[i] = new InfoCenterResult(baseURL, el);
monitor.worked(1);
}
collector.accept(results);
}
private URL createURL(String query, Scope scope) {
if (scope.url == null) {
return null;
}
StringBuilder buf = new StringBuilder();
buf.append(scope.url);
if (!scope.url.endsWith("/")) //$NON-NLS-1$
buf.append("/search?phrase="); //$NON-NLS-1$
else
buf.append("search?phrase="); //$NON-NLS-1$
try {
buf.append(URLEncoder.encode(query, "UTF-8")); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
buf.append(query);
}
buf.append("&locale="); //$NON-NLS-1$
buf.append(Platform.getNL());
if (scope.searchSelected && scope.tocs != null) {
buf.append("&scopedSearch=true"); //$NON-NLS-1$
for (int i = 0; i < scope.tocs.length; i++) {
String toc;
try {
toc = URLEncoder.encode(scope.tocs[i], "UTF-8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
toc = scope.tocs[i];
}
buf.append("&scope="); //$NON-NLS-1$
buf.append(toc);
}
}
try {
return new URL(buf.toString());
} catch (MalformedURLException e) {
return null;
}
}
}