Skip to content

Commit

Permalink
Rework Composer plugin to avoid using Everrest based Websocket calls
Browse files Browse the repository at this point in the history
Fixes #5348

Signed-off-by: Kaloyan Raev <kaloyan.r@zend.com>
  • Loading branch information
kaloyan-raev committed Jul 13, 2017
1 parent aff3be6 commit 403d30d
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 163 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Rogue Wave Software, Inc.
* 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:
* Rogue Wave Software, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.composer.ide;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator;
import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter;
import org.eclipse.che.plugin.composer.shared.dto.ComposerOutput;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;

import static org.eclipse.che.plugin.composer.shared.Constants.COMPOSER_CHANNEL_OUTPUT;
import static org.eclipse.che.plugin.composer.shared.Constants.COMPOSER_CHANNEL_SUBSCRIBE;

/**
* A mechanism for handling all messages from the Composer and applying
* registered consumers.
*
* @author Kaloyan Raev
*/
@Singleton
public class ComposerJsonRpcHandler {
private static final String WS_AGENT_ENDPOINT = "ws-agent";

private RequestHandlerConfigurator configurator;

private Set<Consumer<ComposerOutput>> composerOutputConsumers = new HashSet<>();

private boolean isSubscribed = false;

@Inject
public ComposerJsonRpcHandler(RequestHandlerConfigurator configurator) {
this.configurator = configurator;

handleComposerMessages();
}

@Inject
private void subscribe(RequestTransmitter requestTransmitter) {
if (isSubscribed) {
return;
}

requestTransmitter.newRequest()
.endpointId(WS_AGENT_ENDPOINT)
.methodName(COMPOSER_CHANNEL_SUBSCRIBE)
.noParams()
.sendAndSkipResult();

isSubscribed = true;
}

/**
* Adds consumer for the event with {@link ComposerOutput}.
*
* @param consumer
* new consumer
*/
public void addComposerOutputHandler(Consumer<ComposerOutput> consumer) {
composerOutputConsumers.add(consumer);
}

private void handleComposerMessages() {
configurator.newConfiguration()
.methodName(COMPOSER_CHANNEL_OUTPUT)
.paramsAsDto(ComposerOutput.class)
.noResult()
.withConsumer(archetypeOutput -> composerOutputConsumers.forEach(it -> it.accept(archetypeOutput)));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Rogue Wave Software, Inc.
* Copyright (c) 2016-2017 Rogue Wave Software, Inc.
* 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
Expand All @@ -12,26 +12,14 @@

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;

import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.machine.WsAgentStateController;
import org.eclipse.che.ide.api.machine.events.WsAgentStateEvent;
import org.eclipse.che.ide.api.machine.events.WsAgentStateHandler;
import org.eclipse.che.ide.dto.DtoFactory;
import org.eclipse.che.ide.console.CommandConsoleFactory;
import org.eclipse.che.ide.console.DefaultOutputConsole;
import org.eclipse.che.ide.processes.panel.ProcessesPanelPresenter;
import org.eclipse.che.ide.util.loging.Log;
import org.eclipse.che.ide.websocket.MessageBus;
import org.eclipse.che.ide.websocket.WebSocketException;
import org.eclipse.che.ide.websocket.events.MessageHandler;
import org.eclipse.che.plugin.composer.ide.ComposerJsonRpcHandler;
import org.eclipse.che.plugin.composer.shared.dto.ComposerOutput;

import static org.eclipse.che.plugin.composer.shared.Constants.COMPOSER_CHANNEL_NAME;

/**
* Handler which receives messages from the Composer tool.
*
Expand All @@ -40,77 +28,43 @@
@Singleton
public class ComposerOutputHandler {

private final EventBus eventBus;
private final DtoFactory factory;
private final ProcessesPanelPresenter processesPanelPresenter;
private final CommandConsoleFactory commandConsoleFactory;
private final AppContext appContext;

private DefaultOutputConsole outputConsole;

@Inject
public ComposerOutputHandler(EventBus eventBus,
DtoFactory factory,
WsAgentStateController wsAgentStateController,
public ComposerOutputHandler(ComposerJsonRpcHandler composerJsonRpcHandler,
ProcessesPanelPresenter processesPanelPresenter,
CommandConsoleFactory commandConsoleFactory,
AppContext appContext) {
this.eventBus = eventBus;
this.factory = factory;
this.processesPanelPresenter = processesPanelPresenter;
this.commandConsoleFactory = commandConsoleFactory;
this.appContext = appContext;

handleOperations(factory, wsAgentStateController);
}
composerJsonRpcHandler.addComposerOutputHandler(this::onComposerOutput);

private void handleOperations(final DtoFactory factory, final WsAgentStateController wsAgentStateController) {
eventBus.addHandler(WsAgentStateEvent.TYPE, new WsAgentStateHandler() {
@Override
public void onWsAgentStarted(WsAgentStateEvent event) {
wsAgentStateController.getMessageBus().then(new Operation<MessageBus>() {
@Override
public void apply(MessageBus messageBus) throws OperationException {
handleComposerOutput(messageBus);
}
});
}

@Override
public void onWsAgentStopped(WsAgentStateEvent event) {
}
});
outputConsole = (DefaultOutputConsole) commandConsoleFactory.create("Composer");
}

private void handleComposerOutput(final MessageBus messageBus) {
final DefaultOutputConsole outputConsole = (DefaultOutputConsole) commandConsoleFactory.create("Composer");

try {
messageBus.subscribe(COMPOSER_CHANNEL_NAME, new MessageHandler() {
@Override
public void onMessage(String message) {
Log.info(getClass(), message);
ComposerOutput archetypeOutput = factory.createDtoFromJson(message, ComposerOutput.class);
processesPanelPresenter.addCommandOutput(appContext.getDevMachine().getId(), outputConsole);
switch (archetypeOutput.getState()) {
case START:
outputConsole.clearOutputsButtonClicked();
outputConsole.printText(archetypeOutput.getOutput(),"green");
break;
case IN_PROGRESS:
outputConsole.printText(archetypeOutput.getOutput());
break;
case DONE:
outputConsole.printText(archetypeOutput.getOutput(),"green");
break;
case ERROR:
outputConsole.printText(archetypeOutput.getOutput(),"red");
break;
default:
break;
}
}
});
} catch (WebSocketException e) {
e.printStackTrace();
private void onComposerOutput(ComposerOutput output) {
String message = output.getOutput();
switch (output.getState()) {
case START:
processesPanelPresenter.addCommandOutput(appContext.getDevMachine().getId(), outputConsole);
outputConsole.clearOutputsButtonClicked();
outputConsole.printText(message, "green");
break;
case IN_PROGRESS:
outputConsole.printText(message);
break;
case DONE:
outputConsole.printText(message, "green");
break;
case ERROR:
outputConsole.printText(message, "red");
break;
default:
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Rogue Wave Software, Inc.
* Copyright (c) 2016-2017 Rogue Wave Software, Inc.
* 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
Expand All @@ -17,6 +17,7 @@
import org.eclipse.che.api.project.server.type.ProjectTypeDef;
import org.eclipse.che.api.project.server.type.ValueProviderFactory;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.plugin.composer.server.executor.ComposerJsonRpcMessenger;
import org.eclipse.che.plugin.composer.server.projecttype.ComposerProjectGenerator;
import org.eclipse.che.plugin.composer.server.projecttype.ComposerProjectInitializer;
import org.eclipse.che.plugin.composer.server.projecttype.ComposerProjectType;
Expand Down Expand Up @@ -45,5 +46,7 @@ protected void configure() {
Multibinder<ProjectHandler> projectHandlerMultibinder = newSetBinder(binder(), ProjectHandler.class);
projectHandlerMultibinder.addBinding().to(ComposerProjectGenerator.class);
projectHandlerMultibinder.addBinding().to(ComposerProjectInitializer.class);

bind(ComposerJsonRpcMessenger.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Rogue Wave Software, Inc.
* 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:
* Rogue Wave Software, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.composer.server.executor;

import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.AbstractLineConsumer;
import org.eclipse.che.api.core.util.LineConsumer;
import org.eclipse.che.api.core.util.ProcessUtil;
import org.eclipse.che.api.core.util.ValueHolder;
import org.eclipse.che.api.core.util.Watchdog;
import org.eclipse.che.plugin.composer.shared.dto.ComposerOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
* @author Kaloyan Raev
*/
@Singleton
public class ComposerCommandExecutor {

private EventService eventService;

@Inject
public ComposerCommandExecutor(EventService eventService) {
this.eventService = eventService;
}

private static final Logger LOG = LoggerFactory.getLogger(ComposerCommandExecutor.class);

public void execute(String[] commandLine, File workDir)
throws TimeoutException, IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(commandLine).redirectErrorStream(true).directory(workDir);

eventService.publish(new ComposerOutputImpl(String.join(" ", commandLine), ComposerOutput.State.START));

LineConsumer lineConsumer = new AbstractLineConsumer() {
@Override
public void writeLine(String line) throws IOException {
eventService.publish(new ComposerOutputImpl(line, ComposerOutput.State.IN_PROGRESS));
}
};

// process will be stopped after timeout
Watchdog watcher = new Watchdog(10, TimeUnit.MINUTES);

try {
final Process process = pb.start();
final ValueHolder<Boolean> isTimeoutExceeded = new ValueHolder<>(false);
watcher.start(() -> {
isTimeoutExceeded.set(true);
ProcessUtil.kill(process);
});
// consume logs until process ends
ProcessUtil.process(process, lineConsumer);
process.waitFor();
eventService.publish(new ComposerOutputImpl("Done", ComposerOutput.State.DONE));
if (isTimeoutExceeded.get()) {
LOG.error("Command time expired : command-line " + Arrays.toString(commandLine));
eventService.publish(new ComposerOutputImpl("Installing dependencies time expired", ComposerOutput.State.ERROR));
throw new TimeoutException();
} else if (process.exitValue() != 0) {
LOG.error("Command failed : command-line " + Arrays.toString(commandLine));
eventService.publish(new ComposerOutputImpl("Error occurred", ComposerOutput.State.ERROR));
throw new IOException("Process failed. Exit code " + process.exitValue() + " command-line : " + Arrays.toString(commandLine));
}
} finally {
watcher.stop();
}
}

}
Loading

0 comments on commit 403d30d

Please sign in to comment.