blob: 9fc0199975b14c4b8792e47b5bbdd5c99c6b087b [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 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.rj.server.srv.engine;
import static org.eclipse.statet.rj.server.util.ServerUtils.MISSING_ANSWER_STATUS;
import java.io.File;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.login.LoginException;
import org.eclipse.statet.rj.RjClosedException;
import org.eclipse.statet.rj.RjException;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.server.DataCmdItem;
import org.eclipse.statet.rj.server.MainCmdC2SList;
import org.eclipse.statet.rj.server.MainCmdItem;
import org.eclipse.statet.rj.server.MainCmdS2CList;
import org.eclipse.statet.rj.server.RjsComConfig;
import org.eclipse.statet.rj.server.RjsComObject;
import org.eclipse.statet.rj.server.RjsException;
import org.eclipse.statet.rj.server.RjsStatus;
import org.eclipse.statet.rj.server.Server;
import org.eclipse.statet.rj.server.ServerInfo;
import org.eclipse.statet.rj.server.ServerLogin;
import org.eclipse.statet.rj.server.srv.RMIServerControl;
import org.eclipse.statet.rj.server.srvext.Client;
import org.eclipse.statet.rj.server.srvext.ServerAuthMethod;
import org.eclipse.statet.rj.server.srvext.ServerRuntimePlugin;
public class SrvEngineServer implements Server, RjsComConfig.PathResolver {
private static final List<Remote> clients= new CopyOnWriteArrayList<>();
public static void addClient(final Remote remote) {
if (remote != null) {
clients.add(remote);
}
}
public static void removeClient(final Remote remote) {
if (remote != null) {
clients.remove(remote);
}
}
public static boolean isValid(final Remote remote) {
return (remote != null && clients.contains(remote));
}
protected static final Logger LOGGER= Logger.getLogger("org.eclipse.statet.rj.server");
protected static final class RjExitException extends RjClosedException {
private static final long serialVersionUID= 1L;
public RjExitException(final String message) {
super(message);
}
}
protected final RMIServerControl control;
protected SrvEngine srvEngine;
private final String[] userTypes;
private String[] userNames;
protected String workingDirectory;
protected long timestamp;
protected ServerAuthMethod authMethod;
protected final Client serverClient= new Client("rservi", "dummy", (byte) 1);
private final MainCmdC2SList serverC2SList= new MainCmdC2SList();
public SrvEngineServer(final RMIServerControl control, final ServerAuthMethod authMethod) {
if (control == null) {
throw new NullPointerException();
}
this.control= control;
this.userTypes= createUserTypes();
this.userNames= new String[this.userTypes.length];
setUserName(ServerInfo.USER_OWNER, System.getProperty("user.name"));
this.workingDirectory= System.getProperty("user.dir");
this.authMethod= authMethod;
}
protected String[] createUserTypes() {
return new String[] { ServerInfo.USER_OWNER, ServerInfo.USER_CONSOLE };
}
protected void setUserName(final String type, final String name) {
for (int i= 0; i < this.userTypes.length; i++) {
if (this.userTypes[i].equals(type)) {
if ((this.userNames[i] != null) ? !this.userNames[i].equals(name) : name != null) {
final String[] newNames= new String[this.userTypes.length];
System.arraycopy(this.userNames, 0, newNames, 0, this.userTypes.length);
newNames[i]= name;
this.userNames= newNames;
}
return;
}
}
}
public boolean getConfigUnbindOnStartup() {
return true;
}
public void setEngine(final SrvEngine engine) {
if (this.srvEngine != null) {
throw new IllegalStateException();
}
this.srvEngine= engine;
}
public void start(final ServerRuntimePlugin runtimePlugin) throws Exception {
if (this.srvEngine instanceof SrvEnginePluginExtension) {
((SrvEnginePluginExtension) this.srvEngine).addPlugin(runtimePlugin);
}
}
@Override
public int getState() throws RemoteException {
return this.srvEngine.getState();
}
@Override
public int[] getVersion() throws RemoteException {
final int[] internalVersion= this.srvEngine.getVersion();
final int[] version= new int[internalVersion.length];
System.arraycopy(internalVersion, 0, version, 0, internalVersion.length);
return version;
}
@Override
public ServerInfo getInfo() throws RemoteException {
return new ServerInfo(this.control.getName(), this.workingDirectory, this.timestamp,
this.userTypes, this.userNames,
this.srvEngine.getState());
}
protected ServerAuthMethod getAuthMethod(final String command) {
if (command.startsWith("console.")) {
return this.authMethod;
}
throw new UnsupportedOperationException();
}
@Override
public final ServerLogin createLogin(final String command) throws RemoteException {
final ServerAuthMethod authMethod= getAuthMethod(command);
try {
return authMethod.createLogin();
}
catch (final RjException e) {
final String message= "Initializing login failed.";
LOGGER.log(Level.SEVERE, message, e);
throw new RemoteException(message, e);
}
}
protected Client connectClient(final String command, final ServerLogin login) throws RemoteException, LoginException {
final ServerAuthMethod authMethod= getAuthMethod(command);
try {
return authMethod.performLogin(login);
}
catch (final RjException e) {
final String message= "Performing login failed.";
LOGGER.log(Level.SEVERE, message, e);
throw new RemoteException(message, e);
}
}
@Override
public Object execute(final String command, final Map<String, ? extends Object> properties, final ServerLogin login) throws RemoteException, LoginException {
try {
if (command.equals(C_CONSOLE_START)) {
final Client client= connectClient(command, login);
final Object r= this.srvEngine.start(client, properties);
final Object startupTime= properties.get("rj.session.startup.time");
if (startupTime instanceof Long) {
this.timestamp= ((Long) startupTime).longValue();
}
return r;
}
if (command.equals(C_CONSOLE_CONNECT)) {
final Client client= connectClient(command, login);
final Object r= this.srvEngine.connect(client, properties);
return r;
}
return null;
}
finally {
final Client client= this.srvEngine.getCurrentClient();
setUserName(ServerInfo.USER_CONSOLE, ((client != null) ? client.getUsername() : null));
}
}
protected RObject runServerLoopCommand(RjsComObject sendCom, final DataCmdItem sendItem) throws RjException {
if (sendCom != null) {
throw new UnsupportedOperationException("sendComd");
}
DataCmdItem answer= null;
try {
this.serverC2SList.setObjects(sendItem);
sendCom= this.serverC2SList;
WAIT_FOR_ANSWER: while (true) {
// System.out.println("\n>> SEND ======" + Thread.currentThread() + "\n" + sendItem + "==\n");
final RjsComObject receivedCom= runMainLoop(sendCom, null);
// System.out.println("\n<< RECEIVED ==" + Thread.currentThread() + "\n" + receivedCom + "==\n");
sendCom= null;
COM_TYPE: switch (receivedCom.getComType()) {
case RjsComObject.T_PING:
sendCom= RjsStatus.OK_STATUS;
break COM_TYPE;
case RjsComObject.T_STATUS:
final RjsStatus status= (RjsStatus)receivedCom;
if ((status.getCode() & 0xffffff00) == 0) {
switch (status.getCode()) {
case Server.S_LOST:
case Server.S_NOT_STARTED:
case Server.S_STOPPED:
throw new RjExitException(status.getMessage());
}
}
switch (status.getSeverity()) {
case RjsStatus.OK:
break COM_TYPE;
case RjsStatus.INFO:
break COM_TYPE;
case RjsStatus.ERROR:
if (status.getCode() == RjsStatus.ERROR) {
break WAIT_FOR_ANSWER;
}
throw new RjsException(status.getCode(), status.getMessage());
default:
break COM_TYPE;
}
case RjsComObject.T_MAIN_LIST:
final MainCmdS2CList list= (MainCmdS2CList) receivedCom;
MainCmdItem item= list.getItems();
while (item != null) {
if (item == sendItem) {
answer= sendItem;
break COM_TYPE;
}
processServerCmdItem(item);
item= item.next;
}
break COM_TYPE;
}
}
this.serverC2SList.clear();
if (answer == null || !answer.isOK()) {
final RjsStatus status= (answer != null) ? answer.getStatus() : MISSING_ANSWER_STATUS;
throw new RjException("R commands failed: "+status.getMessage()+".");
}
return answer.getData();
}
catch (final Exception e) {
if (e instanceof RjException) {
throw (RjException) e;
}
throw new RjException("An error when executing R command.", e);
}
}
protected void processServerCmdItem(final MainCmdItem item) {
switch (item.getCmdType()) {
case MainCmdItem.T_CONSOLE_READ_ITEM:
LOGGER.log(Level.INFO, "R-PROMPT: " + item.getDataText());
break;
case MainCmdItem.T_CONSOLE_WRITE_ITEM:
LOGGER.log(Level.INFO, "R-OUT (" + item.getOp() + "): " + item.getDataText());
break;
}
}
protected RjsComObject runMainLoop(final RjsComObject com, final Object caller) throws RemoteException {
return this.srvEngine.runMainLoop(this.serverClient, com);
}
@Override
public File resolve(final Remote ref, final String path) throws RjException {
final File file= new File(path);
if (!SrvEngineServer.isValid(ref)) {
throw new RjException("Invalid access.");
}
if (file.isAbsolute()) {
return file;
}
final RObject rwd= runServerLoopCommand(null, new DataCmdItem(DataCmdItem.EVAL_EXPR_DATA, 0,
(byte) -1, "getwd()", null, null, null, null ));
return new File(rwd.getData().getChar(0), file.getPath());
}
}