/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jooby.crash;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.crsh.cli.impl.Delimiter;
import org.crsh.cli.impl.completion.CompletionMatch;
import org.crsh.cli.spi.Completion;
import org.crsh.plugin.PluginContext;
import org.crsh.shell.Shell;
import org.crsh.shell.ShellFactory;
import org.crsh.shell.ShellProcess;
import org.crsh.util.Utils;
import org.jooby.Request;
import org.jooby.WebSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import javaslang.control.Try;
class WebShellHandler implements WebSocket.OnOpen {
/** The logging system. */
private final Logger log = LoggerFactory.getLogger(getClass());
@SuppressWarnings("rawtypes")
@Override
public void onOpen(final Request req, final WebSocket ws) throws Exception {
PluginContext ctx = req.require(PluginContext.class);
ShellFactory factory = ctx.getPlugin(ShellFactory.class);
Shell shell = factory.create(null);
AtomicReference<ShellProcess> process = new AtomicReference<ShellProcess>();
ws.onMessage(msg -> {
Map event = msg.to(Map.class);
String type = (String) event.get("type");
if (type.equals("welcome")) {
log.debug("sending welcome + prompt");
ws.send(event("print", shell.getWelcome()));
ws.send(event("prompt", shell.getPrompt()));
} else if (type.equals("execute")) {
String command = (String) event.get("command");
Integer width = (Integer) event.get("width");
Integer height = (Integer) event.get("height");
process.set(shell.createProcess(command));
SimpleProcessContext context = new SimpleProcessContext(r -> {
Try.run(() -> {
// reset process
process.set(null);
ws.send(event("print", r.get()));
ws.send(event("prompt", shell.getPrompt()));
ws.send(event("end"));
}).onFailure(x -> log.error("error found while sending output", x));
if ("bye".equals(command)) {
ws.close(WebSocket.NORMAL);
}
}, width, height);
log.debug("executing {}", command);
process.get().execute(context);
} else if (type.equals("cancel")) {
ShellProcess p = process.get();
if (p != null) {
log.info("cancelling {}", p);
p.cancel();
}
} else if (type.equals("complete")) {
String prefix = (String) event.get("prefix");
CompletionMatch completion = shell.complete(prefix);
Completion completions = completion.getValue();
Delimiter delimiter = completion.getDelimiter();
StringBuilder sb = new StringBuilder();
List<String> values = new ArrayList<String>();
if (completions.getSize() == 1) {
String value = completions.getValues().iterator().next();
delimiter.escape(value, sb);
if (completions.get(value)) {
sb.append(delimiter.getValue());
}
values.add(sb.toString());
} else {
String commonCompletion = Utils.findLongestCommonPrefix(completions.getValues());
if (commonCompletion.length() > 0) {
delimiter.escape(commonCompletion, sb);
values.add(sb.toString());
} else {
for (Map.Entry<String, Boolean> entry : completions) {
delimiter.escape(entry.getKey(), sb);
values.add(sb.toString());
sb.setLength(0);
}
}
}
log.debug("completing {} with {}", prefix, values);
ws.send(event("complete", values));
}
});
// clean up on close
ws.onClose(status -> {
log.info("closing web-socket");
ShellProcess sp = process.get();
if (sp != null) {
sp.cancel();
}
shell.close();
});
}
private Object event(final String type, final Object data) {
return ImmutableMap.of("type", type, "data", data);
}
private Object event(final String type) {
return ImmutableMap.of("type", type);
}
}