blob: 358ab982c909783c2ebeb53dd5d2d60596f9b641 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Oak Ridge National Laboratory and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.remote.internal.proxy.core;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.remote.core.IRemoteConnectionChangeListener;
import org.eclipse.remote.core.IRemoteConnectionControlService;
import org.eclipse.remote.core.IRemoteConnectionPropertyService;
import org.eclipse.remote.core.IRemoteProcessBuilder;
import org.eclipse.remote.core.IRemoteProcessService;
import org.eclipse.remote.core.IRemoteProxyService;
import org.eclipse.remote.core.RemoteConnectionChangeEvent;
import org.eclipse.remote.core.RemoteServicesUtils;
import org.eclipse.remote.core.exception.RemoteConnectionException;
import org.eclipse.remote.internal.proxy.core.commands.ExecCommand;
import org.eclipse.remote.internal.proxy.core.commands.GetCwdCommand;
import org.eclipse.remote.internal.proxy.core.commands.GetEnvCommand;
import org.eclipse.remote.internal.proxy.core.commands.GetPropertiesCommand;
import org.eclipse.remote.proxy.core.ChannelMultiplexer;
import org.eclipse.remote.proxy.core.MultiplexedChannel;
import org.eclipse.remote.proxy.core.exceptions.ProxyException;
/**
* @since 5.0
*/
public class ProxyConnection implements IRemoteConnectionControlService,
IRemoteConnectionChangeListener, IRemoteProxyService, IRemoteProcessService {
private final boolean logging = false;
public static final String EMPTY_STRING = ""; //$NON-NLS-1$
private String fWorkingDir;
private ChannelMultiplexer channelMux;
private MultiplexedChannel commandChannel;
private boolean isOpen;
private final IRemoteConnection fRemoteConnection;
private final Map<String, String> fEnv = new HashMap<>();
private final Map<String, String> fProperties = new HashMap<>();
private static final Map<IRemoteConnection, ProxyConnection> connectionMap = new HashMap<>();
public ProxyConnection(IRemoteConnection connection) {
fRemoteConnection = connection;
connection.addConnectionChangeListener(this);
}
@Override
public void connectionChanged(RemoteConnectionChangeEvent event) {
if (event.getType() == RemoteConnectionChangeEvent.CONNECTION_REMOVED) {
synchronized (connectionMap) {
connectionMap.remove(event.getConnection());
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.remote.core.IRemoteConnection.Service#getRemoteConnection()
*/
@Override
public IRemoteConnection getRemoteConnection() {
return fRemoteConnection;
}
public static class Factory implements IRemoteConnection.Service.Factory {
/*
* (non-Javadoc)
*
* @see org.eclipse.remote.core.IRemoteConnection.Service.Factory#getService(org.eclipse.remote.core.IRemoteConnection,
* java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T extends IRemoteConnection.Service> T getService(IRemoteConnection connection, Class<T> service) {
// This little trick creates an instance of this class for a connection
// then for each interface it implements, it returns the same object.
// This works because the connection caches the service so only one gets created.
// As a side effect, it makes this class a service too which can be used
// by the this plug-in
if (ProxyConnection.class.equals(service)) {
synchronized (connectionMap) {
ProxyConnection conn = connectionMap.get(connection);
if (conn == null) {
conn = new ProxyConnection(connection);
connectionMap.put(connection, conn);
}
return (T) conn;
}
} else if (IRemoteConnectionControlService.class.equals(service)
|| IRemoteConnectionPropertyService.class.equals(service)
|| IRemoteProcessService.class.equals(service)
|| IRemoteProxyService.class.equals(service)) {
return (T) connection.getService(ProxyConnection.class);
} else {
return null;
}
}
}
@Override
public void setStreams(InputStream in, OutputStream out) {
channelMux = new ChannelMultiplexer("", this, in, out);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.remote.core.IRemoteConnectionControlService#close()
*/
@Override
public synchronized void close() {
fRemoteConnection.fireConnectionChangeEvent(RemoteConnectionChangeEvent.CONNECTION_CLOSED);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.remote.core.IRemoteConnectionControlService#isOpen()
*/
@Override
public boolean isOpen() {
return isOpen;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.remote.core.IRemoteConnectionControlService#open(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public void open(IProgressMonitor monitor) throws RemoteConnectionException {
if (!isOpen && channelMux != null) {
channelMux.start();
try {
commandChannel = channelMux.openChannel();
initialize(monitor);
} catch (RemoteConnectionException | IOException e) {
try {
commandChannel.close();
} catch (IOException e1) {
// Ignore
}
channelMux.shutdown();
throw new RemoteConnectionException(e.getMessage());
}
isOpen = true;
fRemoteConnection.fireConnectionChangeEvent(RemoteConnectionChangeEvent.CONNECTION_OPENED);
}
}
private void initialize(IProgressMonitor monitor) throws RemoteConnectionException {
SubMonitor subMon = SubMonitor.convert(monitor, 30);
fWorkingDir = getCwd(subMon.newChild(10));
if (subMon.isCanceled()) {
throw new RemoteConnectionException("User canceled opening connection");
}
fEnv.putAll(loadEnv(subMon.newChild(10)));
if (subMon.isCanceled()) {
throw new RemoteConnectionException("User canceled opening connection");
}
fProperties.putAll(loadProperties(subMon.newChild(10)));
if (subMon.isCanceled()) {
throw new RemoteConnectionException("User canceled opening connection");
}
}
private String getCwd(IProgressMonitor monitor) throws RemoteConnectionException {
try {
GetCwdCommand cmd = new GetCwdCommand(this);
return cmd.exec(monitor);
} catch (ProxyException e) {
throw new RemoteConnectionException(e.getMessage());
}
}
private Map<String, String> loadEnv(IProgressMonitor monitor) throws RemoteConnectionException {
try {
GetEnvCommand cmd = new GetEnvCommand(this);
return cmd.exec(monitor);
} catch (ProxyException e) {
throw new RemoteConnectionException(e.getMessage());
}
}
private Map<String, String> loadProperties(IProgressMonitor monitor) throws RemoteConnectionException {
try {
GetPropertiesCommand cmd = new GetPropertiesCommand(this);
return cmd.exec(monitor);
} catch (ProxyException e) {
throw new RemoteConnectionException(e.getMessage());
}
}
public Map<String, String> getEnv() {
return Collections.unmodifiableMap(fEnv);
}
public MultiplexedChannel getCommandChannel() {
return commandChannel;
}
public MultiplexedChannel openChannel() throws IOException {
return channelMux.openChannel();
}
private StringBuffer stdout = new StringBuffer();
private StringBuffer stderr = new StringBuffer();
private String executeCommand(List<String> command, IProgressMonitor monitor) throws ProxyException {
try {
final MultiplexedChannel chanA = channelMux.openChannel();
final MultiplexedChannel chanB = channelMux.openChannel();
final MultiplexedChannel chanC = channelMux.openChannel();
new Thread("cmd stdin reader") {
@Override
public void run() {
byte[] buf = new byte[1024];
int n;
try {
while ((n = chanA.getInputStream().read(buf)) >= 0) {
stdout.append(new String(buf, 0, n));
}
} catch (IOException e) {
// Finish
}
}
}.start();
new Thread("cmd stderr reader") {
@Override
public void run() {
byte[] buf = new byte[1024];
int n;
try {
while ((n = chanB.getInputStream().read(buf)) >= 0) {
stderr.append(new String(buf, 0, n));
}
} catch (IOException e) {
// Finish
}
}
}.start();
ExecCommand cmd = new ExecCommand(this, command, getEnv(), getWorkingDirectory(), false, false, chanA.getId(), chanB.getId(), chanC.getId());
cmd.exec(monitor);
DataInputStream status = new DataInputStream(chanC.getInputStream());
int stat = status.readInt();
if (stat == 0) {
return stdout.toString();
}
return stderr.toString();
} catch (IOException e) {
throw new ProxyException(e.getMessage());
}
}
@Override
public String getEnv(String name) {
return getEnv().get(name);
}
@Override
public IRemoteProcessBuilder getProcessBuilder(List<String> command) {
return new ProxyProcessBuilder(this, command);
}
@Override
public IRemoteProcessBuilder getProcessBuilder(String... command) {
return new ProxyProcessBuilder(this, command);
}
@Override
public String getWorkingDirectory() {
return fWorkingDir;
}
@Override
public void setWorkingDirectory(String path) {
if (RemoteServicesUtils.posixPath(path).isAbsolute()) {
fWorkingDir = path;
}
}
}