/*=============================================================================#
# Copyright (c) 2009-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of either (per the licensee's choosing)
# - the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html, or
# - the GNU Lesser General Public License v2.1 or newer
# which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/lgpl.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.rj.server.srvImpl;
import static de.walware.rj.server.srvext.ServerUtil.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 de.walware.rj.RjException;
import de.walware.rj.data.RObject;
import de.walware.rj.server.DataCmdItem;
import de.walware.rj.server.MainCmdC2SList;
import de.walware.rj.server.MainCmdItem;
import de.walware.rj.server.MainCmdS2CList;
import de.walware.rj.server.RjsComConfig;
import de.walware.rj.server.RjsComObject;
import de.walware.rj.server.RjsException;
import de.walware.rj.server.RjsStatus;
import de.walware.rj.server.Server;
import de.walware.rj.server.ServerInfo;
import de.walware.rj.server.ServerLogin;
import de.walware.rj.server.srvext.Client;
import de.walware.rj.server.srvext.ServerAuthMethod;
public class DefaultServerImpl implements Server, RjsComConfig.PathResolver {
private static final List<Remote> clients = new CopyOnWriteArrayList<>();
public static void removeClient(final Remote remote) {
clients.add(remote);
}
public static void addClient(final Remote remote) {
clients.add(remote);
}
public static boolean isValid(final Remote remote) {
return clients.contains(remote);
}
protected static final Logger LOGGER = Logger.getLogger("de.walware.rj.server");
protected final AbstractServerControl control;
protected InternalEngine internalEngine;
private final String[] userTypes;
private String[] userNames;
protected String workingDirectory;
protected long timestamp;
protected ServerAuthMethod consoleAuthMethod;
protected final Client serverClient = new Client("rservi", "dummy", (byte) 1);
private final MainCmdC2SList serverC2SList = new MainCmdC2SList();
public DefaultServerImpl(final AbstractServerControl 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.consoleAuthMethod = 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;
}
}
}
void setEngine(final InternalEngine engine) {
this.internalEngine = engine;
}
@Override
public int getState() throws RemoteException {
return this.internalEngine.getState();
}
@Override
public int[] getVersion() throws RemoteException {
final int[] internalVersion = this.internalEngine.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.internalEngine.getState());
}
protected ServerAuthMethod getAuthMethod(final String command) {
if (command.startsWith("console.")) {
return this.consoleAuthMethod;
}
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.internalEngine.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.internalEngine.connect(client, properties);
return r;
}
return null;
}
finally {
final Client client = this.internalEngine.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) {
final RjsComObject receivedCom = runMainLoop(sendCom, null);
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;
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;
}
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 RjsComObject runMainLoop(final RjsComObject com, final Object caller) throws RemoteException {
return this.internalEngine.runMainLoop(this.serverClient, com);
}
@Override
public File resolve(final Remote ref, final String path) throws RjException {
final File file = new File(path);
if (!DefaultServerImpl.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());
}
}