blob: d9797a4bf566bac72cb256f5f86089c930f41f0d [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2022 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.rj.servi;
import static org.eclipse.statet.rj.servi.RServiUtils.RJ_SERVI_ID;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
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.WarningStatus;
import org.eclipse.statet.internal.rj.servi.server.RServiBackend;
import org.eclipse.statet.rj.RjClosedException;
import org.eclipse.statet.rj.RjException;
import org.eclipse.statet.rj.data.REnvironment;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RReference;
import org.eclipse.statet.rj.data.impl.DefaultRObjectFactory;
import org.eclipse.statet.rj.server.RjsComConfig;
import org.eclipse.statet.rj.server.RjsStatus;
import org.eclipse.statet.rj.server.Server;
import org.eclipse.statet.rj.server.client.AbstractRJComClient;
import org.eclipse.statet.rj.server.client.AbstractRJComClientGraphicActions;
import org.eclipse.statet.rj.server.client.FunctionCallImpl;
import org.eclipse.statet.rj.server.client.RClientGraphicFactory;
import org.eclipse.statet.rj.server.client.RGraphicCreatorImpl;
import org.eclipse.statet.rj.servi.RServi;
import org.eclipse.statet.rj.services.BasicFQRObject;
import org.eclipse.statet.rj.services.FQRObject;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RGraphicCreator;
import org.eclipse.statet.rj.services.RPlatform;
import org.eclipse.statet.rj.services.RService;
/**
* Client side {@link RServi} handler
*/
@NonNullByDefault
public class RServiImpl<THandle> implements RServi, Externalizable {
private class RServiComClient extends AbstractRJComClient {
public RServiComClient() {
}
@Override
protected void initGraphicFactory() {
final Object graphicFactory= RjsComConfig.getProperty("rj.servi.graphicFactory");
final Object actionsFactory= RjsComConfig.getProperty("rj.servi.comClientGraphicActionsFactory");
if (graphicFactory instanceof RClientGraphicFactory) {
setGraphicFactory((RClientGraphicFactory) graphicFactory,
(actionsFactory instanceof AbstractRJComClientGraphicActions.Factory) ?
((AbstractRJComClientGraphicActions.Factory) actionsFactory).create(
this, getRHandle() ) : null );
}
}
@Override
protected void handleServerStatus(final RjsStatus serverStatus,
final ProgressMonitor m) throws StatusException {
switch (serverStatus.getCode()) {
case 0:
return;
case Server.S_DISCONNECTED:
case Server.S_LOST:
case Server.S_STOPPED:
break;
case RjsStatus.ERROR:
throw new StatusException(new ErrorStatus(RJ_SERVI_ID, "Server or IO error."));
default:
throw new IllegalStateException();
}
synchronized (RServiImpl.this) {
if (!isClosed()) {
CommonsRuntime.log(new WarningStatus(RJ_SERVI_ID,
"RServi is disconnected." ));
forceClose();
}
}
throw new StatusException(new ErrorStatus(RJ_SERVI_ID, "RServi is closed."));
}
@Override
protected void handleStatus(final Status status, final ProgressMonitor m) {
if (status.getSeverity() != Status.OK) {
log(status);
}
}
@Override
protected void log(final Status status) {
CommonsRuntime.log(status);
}
}
public static interface PoolRef extends Remote {
int getCheckIntervalMillis() throws RemoteException;
void check(final long accessId) throws RjException, RemoteException;
void returnObject(final long accessId) throws RjException, RemoteException;
}
private long accessId;
private @Nullable PoolRef poolRef;
private @Nullable RServiBackend backend;
private final AbstractRJComClient rjs= new RServiComClient();
private int rjsId;
private @Nullable THandle rHandle;
public RServiImpl(final long accessId, final PoolRef ref, final RServiBackend backend) {
init(accessId, ref, backend);
}
@SuppressWarnings("null") // init in ::readExternal
public RServiImpl() {
}
@Override
public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
init(in.readLong(), (PoolRef)in.readObject(), (RServiBackend)in.readObject());
}
private void init(final long accessId, final PoolRef poolRef, final RServiBackend backend) {
this.accessId= accessId;
this.poolRef= poolRef;
this.backend= backend;
this.rjs.setServer(this.backend, 1);
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeLong(this.accessId);
out.writeObject(this.poolRef);
out.writeObject(this.backend);
}
public void setRHandle(final THandle rHandle) {
if (this.rjs.getRService() != null) {
throw new IllegalStateException("client already initialized.");
}
this.rHandle= rHandle;
}
public THandle getRHandle() {
return this.rHandle;
}
private synchronized void init() throws StatusException {
this.rjsId= RjsComConfig.registerClientComHandler(this.rjs);
try {
final Map<String, Object> properties= new HashMap<>();
this.rjs.initClient(this.rHandle, this, properties, this.rjsId);
this.rjs.setRjsProperties(properties);
final int checkIntervalMillis= this.poolRef.getCheckIntervalMillis();
if (checkIntervalMillis > 0) {
this.rjs.initPeriodicCheck(this::checkPool, checkIntervalMillis);
}
}
catch (final Exception e) {
forceClose();
throw new StatusException(new ErrorStatus(RJ_SERVI_ID,
"An error occurred when initializing RServi instance.",
e ));
}
}
protected void dispose() {
RjsComConfig.unregisterClientComHandler(this.rjsId);
this.backend= null;
this.poolRef= null;
this.rjs.disposeAllGraphics();
}
private void checkPool() {
final PoolRef poolRef= this.poolRef;
if (poolRef == null) {
return;
}
try {
poolRef.check(this.accessId);
}
catch (final RjClosedException e) {
synchronized (this) {
if (!isClosed()) {
CommonsRuntime.log(new WarningStatus(RJ_SERVI_ID,
"Closing of RServi instance enforced by RServi pool.",
e ));
forceClose();
}
}
}
catch (final Exception e) {
synchronized (this) {
if (!isClosed()) {
CommonsRuntime.log(new ErrorStatus(RJ_SERVI_ID,
"An error occurred when running RServi pool check.",
e ));
}
}
}
}
@Override
public boolean isClosed() {
return this.rjs.isClosed();
}
@Override
public synchronized void close() throws StatusException {
if (this.rjs.isClosed()) {
throw new StatusException(new ErrorStatus(RJ_SERVI_ID,
"RServi is already closed." ));
}
try {
this.rjs.setClosed(true);
this.poolRef.returnObject(this.accessId);
}
catch (final Exception e) {
throw new StatusException(new ErrorStatus(RJ_SERVI_ID,
"An error occurred when closing RServi instance.",
e ));
}
finally {
dispose();
}
}
private void forceClose() {
try {
this.rjs.setClosed(true);
this.poolRef.returnObject(this.accessId);
}
catch (final RjClosedException | NoSuchObjectException e) {
}
catch (final Exception e) {
CommonsRuntime.log(new ErrorStatus(RJ_SERVI_ID,
"An error occurred when closing RServi instance.",
e ));
}
finally {
dispose();
}
}
@Override
public RPlatform getPlatform() {
return this.rjs.getRPlatform();
}
@Override
public void evalVoid(final String expression,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
this.rjs.evalVoid(expression, null, m);
}
@Override
public void evalVoid(final String expression, final @Nullable RObject envir,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
this.rjs.evalVoid(expression, envir, m);
}
@Override
public RObject evalData(final String expression,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.evalData(expression, null, null, 0, RService.DEPTH_INFINITE, m);
}
@Override
public RObject evalData(final String expression,
final @Nullable String factoryId, final int options, final int depth,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.evalData(expression, null, factoryId, options, depth, m);
}
@Override
public RObject evalData(final String expression, final @Nullable RObject envir,
final @Nullable String factoryId, final int options, final int depth,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.evalData(expression, envir, factoryId, options, depth, m);
}
@Override
public RObject evalData(final RReference reference,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.evalData(reference, null, 0, -1, m);
}
@Override
public RObject evalData(final RReference reference,
final @Nullable String factoryId, final int options, final int depth,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.evalData(reference, factoryId, options, depth, m);
}
@Override
public @Nullable FQRObject<THandle> findData(final String symbol, final @Nullable RObject env, final boolean inherits,
final @Nullable String factoryId, final int options, final int depth,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
final RObject[] data= this.rjs.findData(symbol, env, inherits, factoryId, options, depth, m);
if (data != null) {
return new BasicFQRObject<>(this.rHandle, (REnvironment) data[1], symbol, data[0]);
}
return null;
}
@Override
public void assignData(final String expression, final RObject data,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
this.rjs.assignData(expression, data, null, m);
}
@Override
public void downloadFile(final OutputStream out, final String fileName,
final int options,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
this.rjs.downloadFile(out, fileName, options, m);
}
@Override
public byte[] downloadFile(final String fileName,
final int options,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
return this.rjs.downloadFile(fileName, options, m);
}
@Override
public void uploadFile(final InputStream in, final long length, final String fileName,
final int options,
final ProgressMonitor m) throws StatusException {
if (this.rjsId == 0) {
init();
}
this.rjs.uploadFile(in, length, fileName, options, m);
}
@Override
public FunctionCall createFunctionCall(final String name) throws StatusException {
if (this.rjsId == 0) {
init();
}
return new FunctionCallImpl(this.rjs, name, DefaultRObjectFactory.INSTANCE);
}
@Override
public RGraphicCreator createRGraphicCreator(final int options) throws StatusException {
if (this.rjsId == 0) {
init();
}
return new RGraphicCreatorImpl(this, this.rjs, options);
}
}