blob: 873c3942f5de2375f22324081c4cd66a1206718c [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2018, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.rhelp.core.server;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.runtime.CommonsRuntime;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.internal.rhelp.core.FIO;
import org.eclipse.statet.internal.rhelp.core.REnvHelpIndex;
import org.eclipse.statet.internal.rhelp.core.RHelpSearchMatchImpl;
import org.eclipse.statet.internal.rhelp.core.RHelpWebapp;
import org.eclipse.statet.internal.rhelp.core.SerUtil;
import org.eclipse.statet.internal.rhelp.core.SerUtil.Controller;
import org.eclipse.statet.rhelp.core.REnvHelpConfiguration;
import org.eclipse.statet.rhelp.core.RHelpCore;
import org.eclipse.statet.rhelp.core.RHelpPage;
import org.eclipse.statet.rhelp.core.RHelpSearchMatch.MatchFragment;
import org.eclipse.statet.rhelp.core.RHelpSearchQuery;
import org.eclipse.statet.rhelp.core.RHelpSearchRequestor;
import org.eclipse.statet.rhelp.core.RPkgHelp;
@NonNullByDefault
public abstract class ServerREnvHelpAccess implements REnvHelpIndex {
protected static final String[] NO_PARAMS= new String[0];
private final URI url;
private final String basePath;
private final String rEnvId;
private final String path;
private final SerUtil serUtil= new SerUtil();
public ServerREnvHelpAccess(final URI uri) {
this.url= uri;
final String uriPath= nonNullAssert(uri.getPath());
final int idStart= uriPath.lastIndexOf('/');
this.basePath= uriPath.substring(0, idStart);
this.rEnvId= uriPath.substring(idStart + 1);
this.path= this.basePath + RHelpWebapp.CONTEXT_PATH +
'/' + ServerApi.API_VERSION_1 +
'/' + this.rEnvId +
'/';
}
@Override
public void dispose() {
}
protected final String getBasePath() {
return this.basePath;
}
protected final String createPath(final String s1) {
final StringBuilder sb= new StringBuilder(this.path.length() + s1.length());
sb.append(this.path);
sb.append(s1);
return sb.toString();
}
protected final String createPath(final String s1, final String s2) {
final StringBuilder sb= new StringBuilder(this.path.length() + s1.length() + s2.length() + 1);
sb.append(this.path);
sb.append(s1);
sb.append('/');
sb.append(s2);
return sb.toString();
}
protected final String createPath(final String s1, final String s2, final String s3) {
final StringBuilder sb= new StringBuilder(this.path.length() + s1.length() + s2.length() + s3.length() + 2);
sb.append(this.path);
sb.append(s1);
sb.append('/');
sb.append(s2);
sb.append('/');
sb.append(s3);
return sb.toString();
}
protected final String createPath(final String s1, final String s2, final String s3, final String s4) {
final StringBuilder sb= new StringBuilder(this.path.length() + s1.length() + s2.length() + s3.length() + s4.length() + 3);
sb.append(this.path);
sb.append(s1);
sb.append('/');
sb.append(s2);
sb.append('/');
sb.append(s3);
sb.append('/');
sb.append(s4);
return sb.toString();
}
protected URI createUrl(final String path) {
try {
return new URI(this.url.getScheme(), null,
this.url.getHost(), this.url.getPort(),
path, null, null);
}
catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
}
protected StatusException onCancelled() {
return new StatusException(Statuses.CANCEL_STATUS);
}
protected StatusException onTimeout(final @Nullable Throwable e) {
return new StatusException(new ErrorStatus(RHelpCore.BUNDLE_ID, TIMEOUT_ERROR,
"R help server access - Timeout.",
e ));
}
protected StatusException onConnectError(final @Nullable Throwable e) {
return new StatusException(new ErrorStatus(RHelpCore.BUNDLE_ID, CONNECT_ERROR,
"R help server access - Failed to connect to server.",
e ));
}
protected StatusException onFailed(final @Nullable Throwable e) {
final Status status= new ErrorStatus(RHelpCore.BUNDLE_ID,
"R help server access - Failed to perform search.",
e );
CommonsRuntime.log(status);
return new StatusException(status);
}
protected abstract @Nullable InputStream getDataStream(final URI url,
final @Nullable String eTag)
throws StatusException, ResponseException;
protected abstract InputStream getDataStream(final URI url, final String[] params, final byte[] requestData)
throws StatusException, ResponseException;
protected abstract byte[] getDataStreamBytes(final URI url, final String[] params,
final int timeout, final @Nullable ProgressMonitor m) throws StatusException,
ResponseException;
public boolean loadREnvHelpData(final REnvHelpConfiguration rEnvConfig,
final long currentStamp, final Controller saveCheck) throws Exception {
try (final InputStream in= getDataStream(
createUrl(createPath(ServerApi.BASIC_DATA)),
ServerApi.createETag(currentStamp) )) {
if (in == null) {
return false;
}
return this.serUtil.save(rEnvConfig, in, saveCheck);
}
}
@Override
public List<RHelpPage> getPagesForTopic(final String topic, final Map<String, RPkgHelp> packageMap,
final int timeout, final @Nullable ProgressMonitor m) throws StatusException {
try (final FIO fio= FIO.get(new ByteArrayInputStream(getDataStreamBytes(
createUrl(createPath(ServerApi.PAGES)), new String[] {
ServerApi.TOPIC_PARAM, topic,
}, timeout, m)))) {
final int n= fio.readInt();
final List<RHelpPage> pages= new ArrayList<>(n);
for (int i= 0; i < n; i++) {
final String pkgName= nonNullAssert(fio.readString());
final String pageName= nonNullAssert(fio.readString());
final RPkgHelp pkgHelp= packageMap.get(pkgName);
if (pkgHelp != null) {
final RHelpPage page= pkgHelp.getPage(pageName);
if (page != null) {
pages.add(page);
}
}
}
return pages;
}
catch (final Exception e) {
// throw handleException(e);
return ImCollections.emptyList();
}
}
@Override
public @Nullable String getHtmlPage(final RPkgHelp pkgHelp, final String pageName,
final @Nullable String queryString,
final int timeout, final @Nullable ProgressMonitor m) throws StatusException {
try (final FIO fio= FIO.get(new ByteArrayInputStream(getDataStreamBytes(
createUrl(createPath(ServerApi.PKGS, pkgHelp.getName(), ServerApi.PAGES, pageName)),
(queryString != null) ? new String[] {
ServerApi.QUERY_STRING_PARAM, queryString,
} : NO_PARAMS,
timeout, m )))) {
return fio.readString();
}
catch (final NotFoundException e) {
return null;
}
catch (final StatusException e) {
throw e;
}
catch (final Exception e) {
throw onFailed(e);
}
}
@Override
public void search(final RHelpSearchQuery searchQuery, final List<RPkgHelp> packageList,
final Map<String, RPkgHelp> packageMap,
final RHelpSearchRequestor requestor) throws StatusException {
final ByteArrayOutputStream requestData= new ByteArrayOutputStream();
try (final FIO fio= FIO.get(requestData)) {
fio.writeInt(searchQuery.getSearchType());
fio.writeString(searchQuery.getSearchString());
fio.writeStringList(searchQuery.getEnabledFields());
fio.writeStringList(searchQuery.getKeywords());
fio.writeStringList(searchQuery.getPackages());
}
catch (final Exception e) {
throw onFailed(e);
}
try (final FIO fio= FIO.get(getDataStream(
createUrl(createPath(ServerApi.SEARCH)), new String[] {
ServerApi.MAX_FRAGMENTS_PARAM, Integer.toString(requestor.getMaxFragments()),
}, requestData.toByteArray() ))) {
while (true) {
final byte matchType= fio.readByte();
switch (matchType) {
case ServerApi.END_MATCH:
return;
case ServerApi.PAGE_MATCH: {
final String pkgName= fio.readNonNullString();
final String pageName= fio.readNonNullString();
final float score= fio.readFloat();
final int matchCount= fio.readInt();
final MatchFragment[] fragments;
if (matchCount >= 0) {
final int nFragments= fio.readInt();
fragments= new @NonNull MatchFragment[nFragments];
for (int i= 0; i < nFragments; i++) {
final String field= fio.readNonNullString();
final String text= fio.readNonNullString();
fragments[i]= new RHelpSearchMatchImpl.Fragment(field, text);
}
}
else {
fragments= null;
}
final RPkgHelp pkgHelp= packageMap.get(pkgName);
if (pkgHelp != null) {
final RHelpPage page= pkgHelp.getPage(pageName);
if (page != null) {
final RHelpSearchMatchImpl match= (matchCount >= 0) ?
new RHelpSearchMatchImpl(page, score, matchCount, fragments) :
new RHelpSearchMatchImpl(page, score);
requestor.matchFound(match);
}
}
continue;
}
default:
throw new IOException("type= " + matchType);
}
}
}
catch (final StatusException e) {
throw e;
}
catch (final Exception e) {
throw onFailed(e);
}
}
}