/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.maven.server.rmi;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.plugin.maven.server.execution.ExecutionException;
import org.eclipse.che.plugin.maven.server.execution.ProcessEvent;
import org.eclipse.che.plugin.maven.server.execution.ProcessExecutor;
import org.eclipse.che.plugin.maven.server.execution.ProcessHandler;
import org.eclipse.che.plugin.maven.server.execution.ProcessListener;
import org.eclipse.che.plugin.maven.server.execution.ProcessOutputType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.rmi.PortableRemoteObject;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
/**
* @author Evgen Vidolob
*/
public abstract class RmiClient<Remote> {
private static final Logger LOG = LoggerFactory.getLogger(RmiClient.class);
private final Class<Remote> remoteClass;
private final Map<Pair<Object, Object>, ProcessInfo> infoMap = new HashMap<>();
public RmiClient(Class<Remote> remoteClass) {
this.remoteClass = remoteClass;
}
public Remote acquire(Object target, Object param) throws Exception {
Ref<RunningInfo> ref = Ref.ofNull();
Pair<Object, Object> key = Pair.of(target, param);
if (!getInfo(ref, key)) {
startProcess(key);
if (ref.isNull()) {
try {
synchronized (ref) {
while (ref.isNull()) {
ref.wait(1000);
}
}
} catch (InterruptedException ignored) {
}
}
}
if (ref.isNull()) {
throw new RuntimeException("Can't get remote proxy for: " + target.toString());
}
RunningInfo info = ref.getValue();
if (info.processHandler == null) {
throw new Exception(info.name);
}
return acquire(info);
}
private Remote acquire(RunningInfo info) throws Exception {
Remote result;
Registry registry = LocateRegistry.getRegistry("localhost", info.port);
java.rmi.Remote lookup = registry.lookup(info.name);
if (java.rmi.Remote.class.isAssignableFrom(remoteClass)) {
Remote entry = remoteClass.isInstance(lookup) ? (Remote)lookup : (Remote)PortableRemoteObject.narrow(lookup, remoteClass);
if (entry == null) {
result = null;
} else {
//TODO add proxy for remote object
result = (Remote)lookup;
}
} else {
result = (Remote)lookup;
}
info.remoteRef = result;
return result;
}
private void startProcess(Pair<Object, Object> key) {
ProcessHandler handler;
try {
ProcessExecutor executor = getExecutor();
handler = executor.execute();
} catch (ExecutionException e) {
removeProcessInfo(key, null, e.getMessage());
return;
}
handler.addProcessListener(getProcessListener(key));
handler.startNotify();
}
private ProcessListener getProcessListener(Pair<Object, Object> key) {
return new ProcessListener() {
@Override
public void onStart(ProcessEvent event) {
ProcessHandler processHandler = event.getProcessHandler();
synchronized (infoMap) {
ProcessInfo info = infoMap.get(key);
if (info instanceof PendingInfo) {
infoMap.put(key, new PendingInfo(processHandler, ((PendingInfo)info).ref));
}
}
}
@Override
public void onText(ProcessEvent event, ProcessOutputType outputType) {
String text = event.getText();
if (text == null) {
text = "";
}
if (outputType == ProcessOutputType.STDERR) {
LOG.error(text);
} else {
LOG.info(text);
}
RunningInfo runningInfo = null;
PendingInfo pendingInfo = null;
synchronized (infoMap) {
ProcessInfo info = infoMap.get(key);
if (info instanceof PendingInfo) {
pendingInfo = (PendingInfo)info;
if (outputType == ProcessOutputType.STDOUT) {
String prefix = "Port/Name:";
if (text.startsWith(prefix)) {
String portName = text.substring(prefix.length()).trim();
int i = portName.indexOf('/');
runningInfo = new RunningInfo(pendingInfo.processHandler, Integer.parseInt(portName.substring(0, i)),
portName.substring(i + 1));
infoMap.put(key, runningInfo);
infoMap.notifyAll();
}
} else if (outputType == ProcessOutputType.STDERR) {
pendingInfo.stderr.append(text);
}
}
}
if (runningInfo != null) {
synchronized (pendingInfo.ref) {
pendingInfo.ref.setValue(runningInfo);
pendingInfo.ref.notifyAll();
}
// todo add ping there
}
}
@Override
public void onProcessTerminated(ProcessEvent event) {
removeProcessInfo(key, event.getProcessHandler(), null);
}
@Override
public void onProcessWillTerminate(ProcessEvent event) {
removeProcessInfo(key, event.getProcessHandler(), null);
}
};
}
private boolean removeProcessInfo(Pair<Object, Object> key, ProcessHandler handler, String message) {
ProcessInfo info;
synchronized (infoMap) {
info = infoMap.get(key);
if (info != null && (handler == null || info.processHandler == handler)) {
infoMap.remove(key);
infoMap.notifyAll();
} else {
info = null;
}
}
if (info instanceof PendingInfo) {
PendingInfo pendingInfo = (PendingInfo)info;
if (pendingInfo.stderr.length() > 0 || pendingInfo.ref.isNull()) {
if (message != null) {
pendingInfo.stderr.append(message);
}
pendingInfo.ref.setValue(new RunningInfo(null, -1, pendingInfo.stderr.toString()));
}
synchronized (pendingInfo.ref) {
pendingInfo.ref.notifyAll();
}
}
return info != null;
}
protected abstract ProcessExecutor getExecutor();
private boolean getInfo(Ref<RunningInfo> ref, Pair<Object, Object> key) {
ProcessInfo info;
synchronized (infoMap) {
info = infoMap.get(key);
try {
while (info != null && (!(info instanceof RunningInfo) ||
info.processHandler.isProcessTerminating() ||
info.processHandler.isProcessTerminated())) {
infoMap.wait(1000);
info = infoMap.get(key);
}
} catch (InterruptedException ignore) {
}
if (info == null) {
infoMap.put(key, new PendingInfo(null, ref));
}
}
if (info instanceof RunningInfo) {
synchronized (ref) {
ref.setValue((RunningInfo)info);
ref.notifyAll();
}
}
return info != null;
}
public void stopAll(boolean wait) {
List<ProcessInfo> processList;
synchronized (infoMap) {
processList = (infoMap.values().stream().filter(info -> info.processHandler != null).collect(Collectors.toList()));
}
if (processList.isEmpty()) {
return;
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<?> future = executorService.submit(() -> {
for (ProcessInfo processInfo : processList) {
processInfo.processHandler.destroyProcess();
}
if (wait) {
for (ProcessInfo processInfo : processList) {
processInfo.processHandler.waitFor();
}
}
});
if (wait) {
try {
future.get();
} catch (InterruptedException ignore) {
} catch (java.util.concurrent.ExecutionException e) {
LOG.error(e.getMessage(), e);
}
}
executorService.shutdown();
}
private static class ProcessInfo {
final ProcessHandler processHandler;
public ProcessInfo(ProcessHandler processHandler) {
this.processHandler = processHandler;
}
}
private static class RunningInfo extends ProcessInfo {
final int port;
final String name;
Object remoteRef;
public RunningInfo(ProcessHandler processHandler, int port, String name) {
super(processHandler);
this.port = port;
this.name = name;
}
@Override
public String toString() {
return "RunnigInfo{" +
"port=" + port +
", name='" + name + '\'' +
'}';
}
}
private static class PendingInfo extends ProcessInfo {
final Ref<RunningInfo> ref;
final StringBuilder stderr = new StringBuilder();
public PendingInfo(ProcessHandler processHandler,
Ref<RunningInfo> ref) {
super(processHandler);
this.ref = ref;
}
@Override
public String toString() {
return "PendingInfo{" +
"ref=" + ref +
'}';
}
}
static {
//set up RMI
System.setProperty("java.rmi.server.hostname", "localhost");
System.setProperty("java.rmi.server.disableHttp", "true");
}
}