| author | Lazar Kirchev | 2011-01-27 09:48:41 (EST) |
|---|---|---|
| committer | Hristo Iliev | 2011-01-28 15:02:24 (EST) |
| commit | dca28ed19a957c7ebbcfacc5648817d212dceb32 (patch) (side-by-side diff) | |
| tree | 642ad855374c7adfca702faa835cffa0075363f7 | |
| parent | e9141bff3ddf3b74a7ffb47e68f6d1e0e5a78ee8 (diff) | |
| download | org.eclipse.virgo.osgi-extensions-dca28ed19a957c7ebbcfacc5648817d212dceb32.zip org.eclipse.virgo.osgi-extensions-dca28ed19a957c7ebbcfacc5648817d212dceb32.tar.gz org.eclipse.virgo.osgi-extensions-dca28ed19a957c7ebbcfacc5648817d212dceb32.tar.bz2 | |
bug 328566: enhanced telnet support over the equinox console
47 files changed, 3945 insertions, 0 deletions
diff --git a/org.eclipse.virgo.osgi.console/.classpath b/org.eclipse.virgo.osgi.console/.classpath new file mode 100644 index 0000000..bbd454f --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/.classpath @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="src" path="src/main/java"/> + <classpathentry kind="src" path="src/test/java"/> + <classpathentry kind="var" path="OSGI_IVY_CACHE/org.junit/com.springsource.org.junit/4.7.0/com.springsource.org.junit-4.7.0.jar" sourcepath="/OSGI_IVY_CACHE/org.junit/com.springsource.org.junit/4.7.0/com.springsource.org.junit-sources-4.7.0.jar"/> + <classpathentry kind="var" path="OSGI_IVY_CACHE/org.eclipse.osgi/org.eclipse.osgi/3.7.0.v20101022/org.eclipse.osgi-3.7.0.v20101022.jar" sourcepath="/OSGI_IVY_CACHE/org.eclipse.osgi/org.eclipse.osgi/3.7.0.v20101022/org.eclipse.osgi-sources-3.7.0.v20101022.jar"/> + <classpathentry kind="var" path="OSGI_IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-2.3.0.jar" sourcepath="/OSGI_IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-sources-2.3.0.jar"/> + <classpathentry kind="var" path="OSGI_IVY_CACHE/org.eclipse.virgo.teststubs/org.eclipse.virgo.teststubs.osgi/2.2.0.D-20101207145338/org.eclipse.virgo.teststubs.osgi-2.2.0.D-20101207145338.jar" sourcepath="OSGI_IVY_CACHE/org.eclipse.virgo.teststubs/org.eclipse.virgo.teststubs.osgi/2.2.0.D-20101207145338/org.eclipse.virgo.teststubs.osgi-sources-2.2.0.D-20101207145338.jar"/> + <classpathentry kind="var" path="OSGI_IVY_CACHE/org.aspectj/com.springsource.org.aspectj.runtime/1.6.6.RELEASE/com.springsource.org.aspectj.runtime-1.6.6.RELEASE.jar" sourcepath="/OSGI_IVY_CACHE/org.aspectj/com.springsource.org.aspectj.runtime/1.6.6.RELEASE/com.springsource.org.aspectj.runtime-sources-1.6.6.RELEASE.jar"/> + <classpathentry kind="output" path="target/classes"/> +</classpath> diff --git a/org.eclipse.virgo.osgi.console/.project b/org.eclipse.virgo.osgi.console/.project new file mode 100644 index 0000000..b8d3068 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.virgo.osgi.console</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.virgo.osgi.console/build.xml b/org.eclipse.virgo.osgi.console/build.xml new file mode 100644 index 0000000..0c6ddcb --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/build.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="org.eclipse.virgo.osgi.console"> + + <property file="${basedir}/../build.properties"/> + <property file="${basedir}/../build.versions"/> + <!--import file="${basedir}/smoke-test.xml"/--> + <import file="${basedir}/../virgo-build/standard/default.xml"/> + + <!--target name="test.do" depends="quality-common.test.do, smoke-test"/--> + +</project> diff --git a/org.eclipse.virgo.osgi.console/ivy.xml b/org.eclipse.virgo.osgi.console/ivy.xml new file mode 100644 index 0000000..10c01c4 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/ivy.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?> +<ivy-module + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd" + version="1.3"> + + <info organisation="org.eclipse.virgo.osgi" module="${ant.project.name}" /> + + <configurations> + <include file="${virgo.build.dir}/common/default-ivy-configurations.xml"/> + </configurations> + + <publications> + <artifact name="${ant.project.name}"/> + <artifact name="${ant.project.name}-sources" type="src" ext="jar"/> + </publications> + + <dependencies> + <dependency org="org.eclipse.osgi" name="org.eclipse.osgi" rev="${org.eclipse.osgi}" conf="compile->compile"/> + <dependency org="org.junit" name="com.springsource.org.junit" rev="${org.junit}" conf="test->runtime"/> + <dependency org="org.easymock" name="com.springsource.org.easymock" rev="${org.easymock}" conf="test->runtime"/> + <dependency org="org.eclipse.virgo.teststubs" name="org.eclipse.virgo.teststubs.osgi" rev="${org.eclipse.virgo.teststubs}" conf="test->runtime"/> + + </dependencies> + +</ivy-module> diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java new file mode 100644 index 0000000..33ea0c0 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * This class serves as an input stream, which wraps the actual input (e.g. from the telnet) and buffers the lines. + */ +public class ConsoleInputStream extends InputStream { + + private final ArrayList<byte[]> buffer = new ArrayList<byte[]>(); + + private byte[] current; + + private int pos; + + private boolean isClosed; + + public synchronized int read() { + while (current == null && buffer.isEmpty() && !isClosed) { + try { + wait(); + } catch (InterruptedException e) { + return -1; + } + } + if (isClosed) { + return -1; + } + + try { + if (current == null) { + current = buffer.remove(0); + return current[pos++] & 0xFF; + } else { + + return current[pos++] & 0xFF; + } + } finally { + if (current != null) { + if (pos == current.length) { + current = null; + pos = 0; + } + } + } + + } + + public int read(byte b[], int off, int len) throws IOException { + if (len == 0) { + return len; + } + int i = read(); + if (i == -1) { + return -1; + } + b[off] = (byte) i; + return 1; + } + + public synchronized void close() throws IOException { + isClosed = true; + notifyAll(); + } + + public synchronized void add(byte[] data) { + if (data.length > 0) { + buffer.add(data); + notify(); + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java new file mode 100644 index 0000000..16a4d76 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class wraps the actual output stream (e.g., a socket output stream) and is responsible for buffering and + * flushing the characters to the actual output stream. + */ +public class ConsoleOutputStream extends OutputStream { + + /** + * A size of the used buffer. + */ + public static final int BUFFER_SIZE = 2048; + + public final static byte CR = (byte) '\r'; + + public final static byte LF = (byte) '\n'; + + OutputStream out; + + OutputStream oldOut; + + private boolean isEcho = true; + + private boolean queueing = false; + + private byte prevByte; + + private byte[] buffer; + + private int pos; + + /** + * Initiates with instance of the output stream to which it will send data. Here it writes to a socket output + * stream. + * + * @param out OutputStream for console output + */ + public ConsoleOutputStream(OutputStream out) { + this.out = out; + buffer = new byte[BUFFER_SIZE]; + pos = 0; + } + + /** + * An implementation of the corresponding abstract method in OutputStream. + */ + public synchronized void write(int i) throws IOException { + + if (!queueing) { + if (isEcho) { + if (i == '\r' || i == '\0') { + queueing = true; + prevByte = (byte) i; + } else if (i == '\n') { + add(CR); + add(LF); + } else { + add(i); + } + } + } else { // awaiting '\n' AFTER '\r', and '\b' AFTER '\0' + if (prevByte == '\0' && i == '\b') { + isEcho = !isEcho; + } else if (isEcho) { + if (prevByte == '\r' && i == '\n') { + add(CR); + add(LF); + } else { + add(CR); + add(LF); + add(i); + } + } + + queueing = false; + flush(); + } + + } + + /** + * Empties the buffer and sends data to the socket output stream. + * + * @throws IOException + */ + public synchronized void flush() throws IOException { + if (pos > 0) { + out.write(buffer, 0, pos); + pos = 0; + } + } + + /** + * Adds a variable of type integer to the buffer. + * + * @param i integer to add + * @throws java.io.IOException if there are problems adding the integer + */ + private void add(int i) throws IOException { + buffer[pos] = (byte) i; + pos++; + + if (pos == buffer.length) { + flush(); + } + } + + /** + * Closes this OutputStream. + * + * @throws IOException + */ + public void close() throws IOException { + out.close(); + } + + /** + * Substitutes the output stream. The old one is stored so that it can be restored later. + * + * @param newOut new output stream to use. + */ + public void setOutput(OutputStream newOut) { + if (newOut != null) { + oldOut = out; + out = newOut; + } else { + out = oldOut; + } + + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java new file mode 100644 index 0000000..06bd6e6 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.Scanner; + +/** + * This class represents a generic handler of content, read from some input stream. It reads from the stream, and passes + * what is read to a processor, which performs some actions on the content, eventually writing to an output stream. This + * handler should be customized with a concrete content processor. + */ +public abstract class InputHandler extends Thread { + + protected Scanner inputScanner; + + protected OutputStream out; + + protected ConsoleInputStream in; + + protected InputStream input; + + protected byte[] buffer; + + protected static final int MAX_SIZE = 2048; + + public InputHandler(InputStream input, ConsoleInputStream in, OutputStream out) { + this.input = input; + this.in = in; + this.out = out; + buffer = new byte[MAX_SIZE]; + } + + public void run() { + int count; + try { + while ((count = input.read(buffer)) > -1) { + for (int i = 0; i < count; i++) { + inputScanner.scan(buffer[i]); + } + } + } catch (IOException e) { + // Printing stack trace is not needed since the streams are closed immediately + // do nothing + } finally { + try { + in.close(); + } catch (IOException e1) { + // do nothing + } + try { + out.close(); + } catch (IOException e1) { + // do nothing + } + } + } + + public Scanner getScanner() { + return inputScanner; + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java new file mode 100644 index 0000000..0a1636a --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +public enum KEYS { + UP, DOWN, RIGHT, LEFT, CENTER, HOME, END, PGUP, PGDN, INS, DEL, UNFINISHED, UNKNOWN +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java new file mode 100644 index 0000000..903f480 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.KEYS; +import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.SCOTerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT100TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT220TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT320TerminalTypeMappings; + +/** + * A common superclass for content processor for the telnet protocol and for command line editing (processing delete, + * backspace, arrows, command history, etc.). + */ +public abstract class Scanner { + + protected static final byte BS = 8; + + private byte BACKSPACE; + + protected static final byte LF = 10; + + protected static final byte CR = 13; + + protected static final byte ESC = 27; + + protected static final byte SPACE = 32; + + private byte DEL; + + protected static final byte MAX_CHAR = 127; + + protected static final String DEFAULT_TTYPE = File.separatorChar == '/' ? "XTERM" : "ANSI"; + + protected OutputStream toTelnet; + + protected ConsoleInputStream toShell; + + protected Map<String, KEYS> currentEscapesToKey; + + protected final Map<String, TerminalTypeMappings> supportedEscapeSequences; + + protected String[] escapes; + + public Scanner(ConsoleInputStream toShell, OutputStream toTelnet) { + this.toShell = toShell; + this.toTelnet = toTelnet; + supportedEscapeSequences = new HashMap<String, TerminalTypeMappings>(); + supportedEscapeSequences.put("ANSI", new ANSITerminalTypeMappings()); + supportedEscapeSequences.put("VT100", new VT100TerminalTypeMappings()); + VT220TerminalTypeMappings vtMappings = new VT220TerminalTypeMappings(); + supportedEscapeSequences.put("VT220", new VT220TerminalTypeMappings()); + supportedEscapeSequences.put("XTERM", vtMappings); + supportedEscapeSequences.put("VT320", new VT320TerminalTypeMappings()); + supportedEscapeSequences.put("SCO", new SCOTerminalTypeMappings()); + } + + public abstract void scan(int b) throws IOException; + + protected void echo(int b) throws IOException { + toTelnet.write(b); + } + + protected void flush() throws IOException { + toTelnet.flush(); + } + + protected KEYS checkEscape(String possibleEsc) { + if (currentEscapesToKey.get(possibleEsc) != null) { + return currentEscapesToKey.get(possibleEsc); + } + + for (String escape : escapes) { + if (escape.startsWith(possibleEsc)) { + return KEYS.UNFINISHED; + } + } + return KEYS.UNKNOWN; + } + + protected String esc; + + protected boolean isEsc = false; + + protected void startEsc() { + isEsc = true; + esc = ""; + } + + protected abstract void scanEsc(final int b) throws IOException; + + public byte getBackspace() { + return BACKSPACE; + } + + public void setBackspace(byte backspace) { + BACKSPACE = backspace; + } + + public byte getDel() { + return DEL; + } + + public void setDel(byte del) { + DEL = del; + } + + public Map<String, KEYS> getCurrentEscapesToKey() { + return currentEscapesToKey; + } + + public void setCurrentEscapesToKey(Map<String, KEYS> currentEscapesToKey) { + this.currentEscapesToKey = currentEscapesToKey; + } + + public String[] getEscapes() { + if (escapes != null) { + return Arrays.copyOf(escapes, escapes.length); + } else { + return null; + } + } + + public void setEscapes(String[] escapes) { + if (escapes != null) { + this.escapes = Arrays.copyOf(escapes, escapes.length); + } else { + this.escapes = null; + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java new file mode 100644 index 0000000..d00ca53 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +/** + * This is a helper class, which buffers one line of input. It provides for simple line editing - insertion, deletion, + * left and right movement, deletion through the backspace key. + */ +public class SimpleByteBuffer { + + private static int INITAL_SIZE = 13; + + private byte[] buffer; + + private int pos = 0; + + private int size = 0; + + public SimpleByteBuffer() { + buffer = new byte[INITAL_SIZE]; + } + + public void add(final int b) { + if (size >= buffer.length) { + rezize(); + } + buffer[size++] = (byte) b; + } + + private void rezize() { + final byte[] newbuffeer = new byte[buffer.length << 1]; + System.arraycopy(buffer, 0, newbuffeer, 0, buffer.length); + buffer = newbuffeer; + } + + public void insert(int b) { + if (size >= buffer.length) { + rezize(); + } + final int forCopy = size - pos; + if (forCopy > 0) { + System.arraycopy(buffer, pos, buffer, pos + 1, forCopy); + } + buffer[pos++] = (byte) b; + size++; + } + + public int goRight() { + if (pos < size) { + return buffer[pos++] & 0xFF; + } + return -1; + } + + public boolean goLeft() { + if (pos > 0) { + pos--; + return true; + } + return false; + } + + public void delete() { + if (pos < size) { + final int forCopy = size - pos; + System.arraycopy(buffer, pos + 1, buffer, pos, forCopy); + size--; + } + } + + public boolean backSpace() { + if (pos > 0 && size > 0) { + final int forCopy = size - pos; + System.arraycopy(buffer, pos, buffer, pos - 1, forCopy); + size--; + pos--; + return true; + } + return false; + } + + public void delAll() { + pos = 0; + size = 0; + } + + public byte[] getCurrentData() { + byte[] res = new byte[size]; + System.arraycopy(buffer, 0, res, 0, size); + pos = 0; + size = 0; + return res; + } + + public void set(byte[] newData) { + pos = 0; + size = 0; + if (newData != null) { + for (byte data : newData) { + insert(data); + } + } + } + + public int getPos() { + return pos; + } + + public byte[] copyCurrentData() { + byte[] res = new byte[size]; + System.arraycopy(buffer, 0, res, 0, size); + return res; + } + + public int getCurrentChar() { + if (pos < size) { + return buffer[pos] & 0xFF; + } else { + return -1; + } + } + + public int getSize() { + return size; + } + + public int resetPos() { + int res = pos; + pos = 0; + return res; + } + + public void replace(int b) { + if (pos == size) { + insert(b); + } else { + buffer[pos++] = (byte) b; + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java new file mode 100644 index 0000000..42bea12 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.osgi.framework.console.CommandProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This class implements basic command completion. It can complete only OSGi commands, not command parameters. It + * registers a tracker, with which it tracks all CommandProvider services, and when a new one becomes available, it adds + * its command methods in a local cache, which the completer uses to complete the command name given the first letters + * of the command. + */ +public class CommandCompleter { + + private Set<String> availableCommands; + + private ServiceTracker<CommandProvider, Object> cpTracker; + + private BundleContext context = null; + + public CommandCompleter(BundleContext context) { + this.context = context; + availableCommands = new HashSet<String>(); + availableCommands = Collections.synchronizedSet(availableCommands); + availableCommands.add("more"); + availableCommands.add("disconnect"); + availableCommands.add("grep"); + if (context != null) { + cpTracker = new ServiceTracker<CommandProvider, Object>(context, CommandProvider.class.getName(), new CommandProviderCustomizer()); + cpTracker.open(); + } + } + + public String[] complete(String prefix) { + ArrayList<String> candidates = new ArrayList<String>(); + for (String command : availableCommands) { + if (command.startsWith(prefix)) { + candidates.add(command); + } + } + + return candidates.toArray(new String[candidates.size()]); + } + + class CommandProviderCustomizer implements ServiceTrackerCustomizer<CommandProvider, Object> { + + public Object addingService(ServiceReference<CommandProvider> reference) { + CommandProvider provider = context.getService(reference); + Method[] methods = provider.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("_")) { + availableCommands.add(method.getName().substring(1)); + } + } + return null; + } + + public void modifiedService(ServiceReference<CommandProvider> reference, Object service) { + // do nothing + } + + public void removedService(ServiceReference<CommandProvider> reference, Object service) { + CommandProvider provider = context.getService(reference); + Method[] methods = provider.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("_")) { + availableCommands.remove(method.getName()); + } + } + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java new file mode 100644 index 0000000..234b522 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.virgo.osgi.console.supportability.ConsoleInputScanner; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.InputHandler; +import org.osgi.framework.BundleContext; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This class customizes the generic handler with a concrete content processor, which provides command line editing. + */ +public class ConsoleInputHandler extends InputHandler { + + public ConsoleInputHandler(InputStream input, ConsoleInputStream in, OutputStream out, BundleContext context) { + super(input, in, out); + inputScanner = new ConsoleInputScanner(in, out, context); + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java new file mode 100644 index 0000000..7dcab17 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java @@ -0,0 +1,374 @@ +/******************************************************************************* + * Copyright (c) 2010 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.virgo.osgi.console.supportability.CommandCompleter; +import org.eclipse.virgo.osgi.console.supportability.Grep; +import org.eclipse.virgo.osgi.console.supportability.HistoryHolder; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.KEYS; +import org.eclipse.virgo.osgi.console.common.Scanner; +import org.eclipse.virgo.osgi.console.common.SimpleByteBuffer; +import org.osgi.framework.BundleContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * This class performs the processing of the input special characters, and updates respectively what is displayed in the + * output. It handles escape sequences, delete, backspace, arrows, and provides command history and grep. + */ +public class ConsoleInputScanner extends Scanner { + + private static final byte TAB = 9; + + private boolean isCR = false; + + private boolean replace = false; + + private final HistoryHolder history; + + private final SimpleByteBuffer buffer; + + private CommandCompleter completer; + + public ConsoleInputScanner(ConsoleInputStream toShell, OutputStream toTelnet, BundleContext context) { + super(toShell, toTelnet); + history = new HistoryHolder(); + buffer = new SimpleByteBuffer(); + completer = new CommandCompleter(context); + } + + public void scan(int b) throws IOException { + b &= 0xFF; + if (isCR) { + isCR = false; + if (b == LF) { + return; + } + } + if (isEsc) { + scanEsc(b); + } else { + if (b == getBackspace()) { + backSpace(); + } else if (b == TAB) { + tab(); + } else if (b == CR) { + isCR = true; + processData(); + } else if (b == LF) { + processData(); + } else if (b == ESC) { + startEsc(); + } else if (b == getDel()) { + delete(); + } else { + if (b >= SPACE && b < MAX_CHAR) { + newChar(b); + } + } + } + } + + private void delete() throws IOException { + clearLine(); + buffer.delete(); + echoBuff(); + flush(); + } + + private void backSpace() throws IOException { + clearLine(); + buffer.backSpace(); + echoBuff(); + flush(); + } + + private void tab() throws IOException { + byte[] cur = buffer.copyCurrentData(); + String currentInput = new String(cur).trim(); + String[] completionCandidates = completer.complete(currentInput); + + if (completionCandidates.length == 1) { + String suffix = completionCandidates[0].substring(currentInput.length()); + byte[] completion = suffix.getBytes(); + for (byte symbol : completion) { + buffer.insert(symbol); + echo(symbol); + + } + flush(); + return; + } + + echo(CR); + echo(LF); + flush(); + + if (completionCandidates.length == 0) { + buffer.getCurrentData(); + String errorMessage = "No such command"; + for (byte symbol : errorMessage.getBytes()) { + echo(symbol); + } + } else { + for (String candidate : completionCandidates) { + for (byte symbol : candidate.getBytes()) { + echo(symbol); + } + echo(SPACE); + echo(SPACE); + } + } + + echo(CR); + echo(LF); + flush(); + echo('o'); + echo('s'); + echo('g'); + echo('i'); + echo('>'); + echo(SPACE); + echoBuff(); + flush(); + } + + protected void clearLine() throws IOException { + int size = buffer.getSize(); + int pos = buffer.getPos(); + for (int i = size - pos; i < size; i++) { + echo(BS); + } + for (int i = 0; i < size; i++) { + echo(SPACE); + } + for (int i = 0; i < size; i++) { + echo(BS); + } + } + + protected void echoBuff() throws IOException { + byte[] data = buffer.copyCurrentData(); + for (byte b : data) { + echo(b); + } + int pos = buffer.getPos(); + for (int i = data.length; i > pos; i--) { + echo(BS); + } + } + + protected void newChar(int b) throws IOException { + if (buffer.getPos() < buffer.getSize()) { + if (replace) { + buffer.replace(b); + } else { + buffer.insert(b); + } + clearLine(); + echoBuff(); + flush(); + } else { + if (replace) { + buffer.replace(b); + } else { + buffer.insert(b); + } + } + } + + private void processData() throws IOException { + buffer.add(CR); + buffer.add(LF); + echo(CR); + echo(LF); + flush(); + byte[] curr = buffer.getCurrentData(); + history.add(curr); + + int index = 0; + boolean isGrep = false; + for (; index < curr.length; index++) { + if (curr[index] == '|') { + isGrep = true; + break; + } + } + + if (isGrep) { + byte[] grepExpression = new byte[curr.length - index - 1]; + System.arraycopy(curr, index + 1, grepExpression, 0, grepExpression.length); + Grep grep = new Grep(grepExpression, toTelnet); + grep.start(); + byte[] array = Arrays.copyOf(curr, index + 2); + array[index] = CR; + array[index + 1] = LF; + toShell.add(array); + } else { + toShell.add(curr); + } + } + + public void resetHistory() { + history.reset(); + } + + protected void scanEsc(final int b) throws IOException { + esc += (char) b; + KEYS key = checkEscape(esc); + if (key == KEYS.UNFINISHED) { + return; + } + if (key == KEYS.UNKNOWN) { + isEsc = false; + scan(b); + return; + } + isEsc = false; + switch (key) { + case UP: + processUpArrow(); + break; + case DOWN: + processDownArrow(); + break; + case RIGHT: + processRightArrow(); + break; + case LEFT: + processLeftArrow(); + break; + case HOME: + processHome(); + break; + case END: + processEnd(); + break; + case PGUP: + processPgUp(); + break; + case PGDN: + processPgDn(); + break; + case INS: + processIns(); + break; + case DEL: + delete(); + break; + default: // CENTER + break; + } + } + + private static final byte[] INVERSE_ON = { ESC, '[', '7', 'm' }; + + private static final byte[] INVERSE_OFF = { ESC, '[', '2', '7', 'm' }; + + private void echo(byte[] data) throws IOException { + for (byte b : data) { + echo(b); + } + } + + private void processIns() throws IOException { + replace = !replace; + int b = buffer.getCurrentChar(); + echo(INVERSE_ON); + echo(replace ? 'R' : 'I'); + flush(); + try { + Thread.sleep(300); + } catch (InterruptedException e) { + // do not care $JL-EXC$ + } + echo(INVERSE_OFF); + echo(BS); + echo(b == -1 ? SPACE : b); + echo(BS); + flush(); + } + + private void processPgDn() throws IOException { + byte[] last = history.last(); + if (last != null) { + clearLine(); + buffer.set(last); + echoBuff(); + flush(); + } + } + + private void processPgUp() throws IOException { + byte[] first = history.first(); + if (first != null) { + clearLine(); + buffer.set(first); + echoBuff(); + flush(); + } + } + + private void processHome() throws IOException { + int pos = buffer.resetPos(); + if (pos > 0) { + for (int i = 0; i < pos; i++) { + echo(BS); + } + flush(); + } + } + + private void processEnd() throws IOException { + int b; + while ((b = buffer.goRight()) != -1) { + echo(b); + } + flush(); + } + + private void processLeftArrow() throws IOException { + if (buffer.goLeft()) { + echo(BS); + flush(); + } + } + + private void processRightArrow() throws IOException { + int b = buffer.goRight(); + if (b != -1) { + echo(b); + flush(); + } + } + + private void processDownArrow() throws IOException { + byte[] next = history.next(); + if (next != null) { + clearLine(); + buffer.set(next); + echoBuff(); + flush(); + } + } + + private void processUpArrow() throws IOException { + clearLine(); + byte[] prev = history.prev(); + buffer.set(prev); + echoBuff(); + flush(); + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java new file mode 100644 index 0000000..2f26001 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; + +import java.io.*; +import java.util.ArrayList; + +/** + * This class implements grep. Since in Equinox 3.6 there is not piping support, grep cannot be implemented as a shell + * command. That is why it is implemented as part of the command line editing features. The socket output stream inside + * ConsoleOutputStream is substituted with a PipedOutputStream, so that what the command writes to the output stream + * does not go to the console, but is read through a PipedInputStream and is filtered for the searched expression. After + * all output of the command is read and filtered, the socket output stream inside ConsoleOutputStream is restored and + * the lines of the command output, which match the grep expression, are written to it. + */ +public class Grep extends Thread { + + private String expression; + + private ConsoleOutputStream out; + + private PipedInputStream input; + + private PipedOutputStream output; + + private BufferedReader reader; + + private ArrayList<String> filteredOutput; + + private static int LENGTH = 4; + + public Grep(byte[] expression, OutputStream out) { + String expr = (new String(expression)).trim(); + int index = expr.indexOf("grep"); + this.expression = expr.substring(index + LENGTH).trim(); + this.out = (ConsoleOutputStream) out; + input = new PipedInputStream(); + try { + output = new PipedOutputStream(input); + } catch (IOException e) { + e.printStackTrace(); + } + this.out.setOutput(output); + filteredOutput = new ArrayList<String>(); + } + + public void run() { + reader = new BufferedReader(new InputStreamReader(input)); + boolean hasMore = true; + try { + while (hasMore) { + String line = getLine(); + hasMore = line != null; + if (hasMore) { + // last line containing the osgi prompt should be output although it does not contain the grep + // expression + if (line.contains(expression) || line.contains("osgi>")) { + filteredOutput.add(line); + } + + if (line.contains("osgi>")) { + hasMore = false; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + out.setOutput(null); + PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out))); + for (int i = 0; i < filteredOutput.size(); i++) { + if (i == filteredOutput.size() - 1 && filteredOutput.get(i).contains("osgi>")) { + writer.print(filteredOutput.get(i)); + writer.print(" "); + } else { + writer.println(filteredOutput.get(i)); + } + writer.flush(); + } + } + + private String getLine() throws IOException { + StringBuilder line = new StringBuilder(); + boolean quit = false; + while (!quit) { + int c = reader.read(); + if (c < 0) { + quit = true; + } else { + switch (c) { + case '\r': + break; + case '\n': + return line.toString(); + default: + line.append((char) c); + if (line.toString().contains("osgi>")) { + return line.toString(); + } + break; + } + } + } + + return null; + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java new file mode 100644 index 0000000..dc39af9 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * A helper class, which implements history. + */ +public class HistoryHolder { + + private static final int MAX = 100; + + private final byte[][] history; + + private int size; + + private int pos; + + public HistoryHolder() { + history = new byte[MAX][]; + } + + public synchronized void reset() { + size = 0; + pos = 0; + for (int i = 0; i < MAX; i++) { + history[i] = null; + } + } + + public synchronized void add(byte[] data) { + try { + data = new String(data, "US-ASCII").trim().getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e) { + + } + if (data.length == 0) { + pos = size; + return; + } + for (int i = 0; i < size; i++) { + if (Arrays.equals(history[i], data)) { + System.arraycopy(history, i + 1, history, i, size - i - 1); + history[size - 1] = data; + pos = size; + return; + } + } + if (size >= MAX) { + System.arraycopy(history, 1, history, 0, size - 1); + size--; + } + history[size++] = data; + pos = size; + } + + public synchronized byte[] next() { + if (pos >= size - 1) { + return null; + } + return history[++pos]; + } + + public synchronized byte[] last() { + if (size > 0) { + pos = size - 1; + return history[pos]; + } else { + return null; + } + } + + public synchronized byte[] first() { + if (size > 0) { + pos = 0; + return history[pos]; + } else { + return null; + } + } + + public synchronized byte[] prev() { + if (size == 0) { + return null; + } + if (pos == 0) { + return history[pos]; + } else { + return history[--pos]; + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java new file mode 100644 index 0000000..9468f7f --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.common.KEYS; + +public class ANSITerminalTypeMappings extends TerminalTypeMappings { + + public ANSITerminalTypeMappings() { + super(); + BACKSPACE = 8; + DEL = 127; + } + + public void setKeypadMappings() { + escapesToKey.put("[1~", KEYS.HOME); //$NON-NLS-1$ + escapesToKey.put("[4~", KEYS.END); //$NON-NLS-1$ + escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$ + escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$ + escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$ + escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$ + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java new file mode 100644 index 0000000..6b2474e --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +public interface Callback { + + public void finished(); +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java new file mode 100644 index 0000000..883c6ea --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.Callback; +import org.eclipse.virgo.osgi.console.telnet.TelnetConsoleSession; + +public class NegotiationFinishedCallback implements Callback { + + private TelnetConsoleSession consoleSession; + + public NegotiationFinishedCallback(TelnetConsoleSession consoleSession) { + this.consoleSession = consoleSession; + } + + @Override + public void finished() { + consoleSession.telnetNegotiationFinished(); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java new file mode 100644 index 0000000..b337e99 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.common.KEYS; + +public class SCOTerminalTypeMappings extends TerminalTypeMappings { + + public SCOTerminalTypeMappings() { + super(); + + BACKSPACE = -1; + DEL = 127; + } + + @Override + public void setKeypadMappings() { + escapesToKey.put("[H", KEYS.HOME); + escapesToKey.put("F", KEYS.END); + escapesToKey.put("[L", KEYS.INS); + escapesToKey.put("[I", KEYS.PGUP); + escapesToKey.put("[G", KEYS.PGDN); + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java new file mode 100644 index 0000000..22758c9 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.Callback; +import org.eclipse.virgo.osgi.console.telnet.NegotiationFinishedCallback; +import org.eclipse.virgo.osgi.console.telnet.TelnetInputHandler; +import org.eclipse.virgo.osgi.console.telnet.TelnetOutputStream; +import org.eclipse.osgi.framework.console.ConsoleSession; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.supportability.ConsoleInputHandler; +import org.osgi.framework.BundleContext; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * This class provides an implementation of a ConsoleSession. It creates a handler for the input from telnet and wraps + * its streams to add handling for command line editing. + */ +public class TelnetConsoleSession extends ConsoleSession { + + private final Socket s; + + private InputStream input; + + private final ConsoleInputStream in; + + private final TelnetOutputStream out; + + protected boolean isTelnetNegotiationFinished = false; + + private Callback callback; + + private static final long TIMEOUT = 1000; + + private static final long NEGOTIATION_TIMEOUT = 60000; + + private final BundleContext context; + + public TelnetConsoleSession(Socket s, BundleContext context) throws IOException { + in = new ConsoleInputStream(); + out = new TelnetOutputStream(s.getOutputStream()); + out.autoSend(); + this.s = s; + this.context = context; + + callback = new NegotiationFinishedCallback(this); + } + + public synchronized void start() throws IOException { + TelnetInputHandler telnetInputHandler = new TelnetInputHandler(s.getInputStream(), in, out, callback); + telnetInputHandler.start(); + long start = System.currentTimeMillis(); + while (isTelnetNegotiationFinished == false && System.currentTimeMillis() - start < NEGOTIATION_TIMEOUT) { + try { + wait(TIMEOUT); + } catch (InterruptedException e) { + // do nothing + } + } + + ConsoleInputStream inp = new ConsoleInputStream(); + + ConsoleInputHandler consoleInputHandler = new ConsoleInputHandler(in, inp, out, context); + consoleInputHandler.getScanner().setBackspace(telnetInputHandler.getScanner().getBackspace()); + consoleInputHandler.getScanner().setDel(telnetInputHandler.getScanner().getDel()); + consoleInputHandler.getScanner().setCurrentEscapesToKey(telnetInputHandler.getScanner().getCurrentEscapesToKey()); + consoleInputHandler.getScanner().setEscapes(telnetInputHandler.getScanner().getEscapes()); + + consoleInputHandler.start(); + input = inp; + } + + public synchronized void telnetNegotiationFinished() { + isTelnetNegotiationFinished = true; + notify(); + + } + + public synchronized InputStream getInput() { + return input; + } + + public synchronized OutputStream getOutput() { + return out; + } + + public synchronized void doClose() { + if (s != null) { + try { + s.close(); + } catch (IOException ioe) { + // do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + // do nothing + } + } + + if (input != null) { + try { + input.close(); + } catch (IOException ioe) { + // do nothing + } + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java new file mode 100644 index 0000000..8ab3f3c --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.Callback; +import org.eclipse.virgo.osgi.console.telnet.TelnetInputScanner; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.eclipse.virgo.osgi.console.common.InputHandler; + +import java.io.InputStream; + +/** + * This class customizes the generic handler with a concrete content processor, which provides telnet protocol handling. + */ +public class TelnetInputHandler extends InputHandler { + + public TelnetInputHandler(InputStream input, ConsoleInputStream in, ConsoleOutputStream out, Callback callback) { + super(input, in, out); + inputScanner = new TelnetInputScanner(in, out, callback); + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java new file mode 100644 index 0000000..8f865b9 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java @@ -0,0 +1,296 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.Callback; +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.eclipse.virgo.osgi.console.common.KEYS; +import org.eclipse.virgo.osgi.console.common.Scanner; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * This class performs the processing of the telnet commands, and updates respectively what is displayed in the output. + */ +public class TelnetInputScanner extends Scanner { + + private boolean isCommand = false; + + private boolean isReadingTtype = false; + + private boolean shouldFinish = false; + + private boolean tTypeNegotiationStarted = false; + + private int lastRead = -1; + + private ArrayList<Integer> currentTerminalType = new ArrayList<Integer>(); + + private ArrayList<Integer> lastTerminalType = null; + + private Set<String> supportedTerminalTypes = new HashSet<String>(); + + private Callback callback; + + public TelnetInputScanner(ConsoleInputStream toShell, ConsoleOutputStream toTelnet, Callback callback) { + super(toShell, toTelnet); + initializeSupportedTerminalTypes(); + TerminalTypeMappings currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE); + currentEscapesToKey = currentMapping.getEscapesToKey(); + escapes = currentMapping.getEscapes(); + setBackspace(currentMapping.getBackspace()); + setDel(currentMapping.getDel()); + this.callback = callback; + } + + private void initializeSupportedTerminalTypes() { + supportedTerminalTypes.add("ANSI"); + supportedTerminalTypes.add("VT100"); + supportedTerminalTypes.add("VT220"); + supportedTerminalTypes.add("VT320"); + supportedTerminalTypes.add("XTERM"); + supportedTerminalTypes.add("SCO"); + } + + public void scan(int b) throws IOException { + b &= 0xFF; + + if (isEsc) { + scanEsc(b); + } else if (isCommand) { + scanCommand(b); + } else if (b == IAC) { + startCommand(); + } else { + switch (b) { + case ESC: + startEsc(); + toShell.add(new byte[] { (byte) b }); + break; + default: + if (b >= SPACE && b < MAX_CHAR) { + echo((byte) b); + flush(); + } + toShell.add(new byte[] { (byte) b }); + } + + } + lastRead = b; + } + + /* + * Telnet command codes are described in RFC 854, TELNET PROTOCOL SPECIFICATION available at + * http://www.ietf.org/rfc/rfc854.txt + * + * Telnet terminal type negotiation option is described in RFC 1091, Telnet Terminal-Type Option available at + * http://www.ietf.org/rfc/rfc1091.txt + */ + private static final int SE = 240; + + private static final int EC = 247; + + private static final int EL = 248; + + private static final int SB = 250; + + private static final int WILL = 251; + + private static final int WILL_NOT = 252; + + private static final int DO = 253; + + private static final int DO_NOT = 254; + + private static final int TTYPE = 24; + + private static final int SEND = 1; + + private static final int IAC = 255; + + private static final int IS = 0; + + private boolean isNegotiation; + + private boolean isWill; + + private byte[] tTypeRequest = { (byte) IAC, (byte) SB, (byte) TTYPE, (byte) SEND, (byte) IAC, (byte) SE }; + + private void scanCommand(final int b) throws IOException { + if (isNegotiation) { + scanNegotiation(b); + } else if (isWill) { + isWill = false; + isCommand = false; + if (b == TTYPE && tTypeNegotiationStarted == false) { + sendRequest(); + } + } else { + switch (b) { + case WILL: + isWill = true; + break; + case WILL_NOT: + break; + case DO: + break; + case DO_NOT: + break; + case SB: + isNegotiation = true; + break; + case EC: + eraseChar(); + isCommand = false; + break; + case EL: + default: + isCommand = false; + break; + } + } + } + + private void scanNegotiation(final int b) { + if (lastRead == SB && b == TTYPE) { + isReadingTtype = true; + } else if (b == IS) { + + } else if (b == IAC) { + + } else if (b == SE) { + isNegotiation = false; + isCommand = false; + if (isReadingTtype == true) { + isReadingTtype = false; + if (shouldFinish == true) { + setCurrentTerminalType(); + shouldFinish = false; + return; + } + boolean isMatch = isTerminalTypeSupported(); + boolean isLast = isLast(); + if (isMatch == true) { + setCurrentTerminalType(); + return; + } + lastTerminalType = currentTerminalType; + currentTerminalType = new ArrayList<Integer>(); + if (isLast == true && isMatch == false) { + shouldFinish = true; + sendRequest(); + } else if (isLast == false && isMatch == false) { + sendRequest(); + } + } + } else if (isReadingTtype == true) { + currentTerminalType.add(b); + } + } + + private boolean isTerminalTypeSupported() { + byte[] tmp = new byte[currentTerminalType.size()]; + int idx = 0; + for (Integer i : currentTerminalType) { + tmp[idx] = i.byteValue(); + idx++; + } + String tType = new String(tmp); + + for (String terminal : supportedTerminalTypes) { + if (tType.toUpperCase().contains(terminal)) { + return true; + } + } + + return false; + } + + private boolean isLast() { + if (currentTerminalType.equals(lastTerminalType)) { + return true; + } else { + return false; + } + } + + private void setCurrentTerminalType() { + byte[] tmp = new byte[currentTerminalType.size()]; + int idx = 0; + for (Integer i : currentTerminalType) { + tmp[idx] = i.byteValue(); + idx++; + } + String tType = new String(tmp); + String term = null; + for (String terminal : supportedTerminalTypes) { + if (tType.toUpperCase().contains(terminal)) { + term = terminal; + } + } + TerminalTypeMappings currentMapping = supportedEscapeSequences.get(term); + if (currentMapping == null) { + currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE); + } + currentEscapesToKey = currentMapping.getEscapesToKey(); + escapes = currentMapping.getEscapes(); + setBackspace(currentMapping.getBackspace()); + setDel(currentMapping.getDel()); + if (callback != null) { + callback.finished(); + } + } + + private void sendRequest() { + try { + toTelnet.write(tTypeRequest); + toTelnet.flush(); + if (tTypeNegotiationStarted == false) { + tTypeNegotiationStarted = true; + } + } catch (IOException e) { + + e.printStackTrace(); + } + } + + private void startCommand() { + isCommand = true; + isNegotiation = false; + isWill = false; + } + + private void eraseChar() throws IOException { + toShell.add(new byte[] { BS }); + } + + protected void scanEsc(int b) throws IOException { + esc += (char) b; + toShell.add(new byte[] { (byte) b }); + KEYS key = checkEscape(esc); + if (key == KEYS.UNFINISHED) { + return; + } + if (key == KEYS.UNKNOWN) { + isEsc = false; + scan(b); + return; + } + isEsc = false; + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java new file mode 100644 index 0000000..16c870c --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.TelnetConsoleSession; +import org.eclipse.osgi.framework.console.ConsoleSession; +import org.osgi.framework.BundleContext; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; + +public class TelnetManager { + + private ConsoleSocketGetter csg; + + private int telnetPort = -1; + + private String host = null; + + private BundleContext context = null; + + private static final String PROP_CONSOLE = "osgi.console"; + + public TelnetManager(BundleContext bundleContext) { + String consoleValue = null; + try { + consoleValue = bundleContext.getProperty(PROP_CONSOLE); + if (consoleValue != null && !"".equals(consoleValue) && !"none".equals(consoleValue)) { + parseHostAndPort(consoleValue); + } + } catch (NumberFormatException e) { + System.out.println("Invalid host/port in " + consoleValue + "; " + e.getMessage()); + e.printStackTrace(); + } + + context = bundleContext; + } + + public void startConsoleListener() { + if (telnetPort != -1) { + try { + if (host != null) { + csg = new ConsoleSocketGetter(new ServerSocket(telnetPort, 0, InetAddress.getByName(host)), context); + } else { + csg = new ConsoleSocketGetter(new ServerSocket(telnetPort), context); + } + } catch (IOException e) { + System.out.println("Unable to open telnet. Reason: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + public void stop() { + if (csg != null) { + csg.shutdown(); + } + } + + public BundleContext getContext() { + return context; + } + + private void parseHostAndPort(String consoleValue) { + int index = consoleValue.lastIndexOf(":"); + if (index > -1) { + host = consoleValue.substring(0, index); + } + + telnetPort = Integer.parseInt(consoleValue.substring(index + 1)); + } + + /** + * ConsoleSocketGetter - provides a Thread that listens on the port for telnet connections. + */ + static class ConsoleSocketGetter implements Runnable { + + /** + * The ServerSocket to accept connections from + */ + private final ServerSocket server; + + private final BundleContext context; + + private volatile boolean shutdown = false; + + /** + * Constructor - sets the server and starts the thread to listen for connections. + * + * @param server a ServerSocket to accept connections from + * @param context Bundle context + */ + ConsoleSocketGetter(ServerSocket server, BundleContext context) { + this.server = server; + this.context = context; + try { + Method reuseAddress = server.getClass().getMethod("setReuseAddress", new Class[] { boolean.class }); //$NON-NLS-1$ + reuseAddress.invoke(server, Boolean.TRUE); + } catch (Exception ex) { + // try to set the socket re-use property, it isn't a problem if it can't be set + } + Thread t = new Thread(this, "ConsoleSocketGetter"); //$NON-NLS-1$ + t.setDaemon(true); + t.start(); + } + + public void run() { + // Print message containing port console actually bound to.. + System.out.println("Listening on port: " + Integer.toString(server.getLocalPort())); + while (!shutdown) { + try { + Socket socket = server.accept(); + if (socket == null) + throw new IOException("No socket available. Probably caused by a shutdown."); //$NON-NLS-1$ + + TelnetConsoleSession session = new TelnetConsoleSession(socket, context); + session.start(); // start the Input Handler + context.registerService(ConsoleSession.class.getName(), session, null); + } catch (Exception e) { + if (!shutdown) + e.printStackTrace(); + } + } + } + + public void shutdown() { + if (shutdown) + return; + shutdown = true; + try { + server.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java new file mode 100644 index 0000000..9dced41 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class adds to the output stream wrapper initial negotiation of telnet communication. + */ +public class TelnetOutputStream extends ConsoleOutputStream { + + static final byte[] autoMessage = new byte[] { (byte) 255, (byte) 251, (byte) 1, // IAC WILL ECHO + (byte) 255, (byte) 251, (byte) 3, // IAC WILL SUPPRESS GO_AHEAD + (byte) 255, (byte) 253, (byte) 31, // IAC DO NAWS + (byte) 255, (byte) 253, (byte) 24 }; // IAC DO TTYPE + + public TelnetOutputStream(OutputStream out) { + super(out); + } + + /** + * Sends the options which a server wants to negotiate with a telnet client. + */ + public synchronized void autoSend() throws IOException { + write(autoMessage); + flush(); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java new file mode 100644 index 0000000..16e8fc3 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.virgo.osgi.console.common.KEYS; + +public abstract class TerminalTypeMappings { + + protected Map<String, KEYS> escapesToKey; + + protected String[] escapes; + + protected byte BACKSPACE; + + protected byte DEL; + + public TerminalTypeMappings() { + escapesToKey = new HashMap<String, KEYS>(); + escapesToKey.put("[A", KEYS.UP); //$NON-NLS-1$ + escapesToKey.put("[B", KEYS.DOWN); //$NON-NLS-1$ + escapesToKey.put("[C", KEYS.RIGHT); //$NON-NLS-1$ + escapesToKey.put("[D", KEYS.LEFT); //$NON-NLS-1$ + escapesToKey.put("[G", KEYS.CENTER); //$NON-NLS-1$ + setKeypadMappings(); + createEscapes(); + } + + public Map<String, KEYS> getEscapesToKey() { + return escapesToKey; + } + + public String[] getEscapes() { + if (escapes != null) { + return Arrays.copyOf(escapes, escapes.length); + } else { + return null; + } + } + + public byte getBackspace() { + return BACKSPACE; + } + + public byte getDel() { + return DEL; + } + + public abstract void setKeypadMappings(); + + private void createEscapes() { + escapes = new String[escapesToKey.size()]; + Object[] temp = escapesToKey.keySet().toArray(); + for (int i = 0; i < escapes.length; i++) { + escapes[i] = (String) temp[i]; + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java new file mode 100644 index 0000000..375452c --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.common.KEYS; + +public class VT100TerminalTypeMappings extends TerminalTypeMappings { + + public VT100TerminalTypeMappings() { + super(); + BACKSPACE = 127; + DEL = -1; + } + + @Override + public void setKeypadMappings() { + escapesToKey.put("[H", KEYS.HOME); //$NON-NLS-1$ + escapesToKey.put("[4~", KEYS.END); //$NON-NLS-1$ + escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$ + escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$ + escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$ + escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$ + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java new file mode 100644 index 0000000..12075f3 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings; + +public class VT220TerminalTypeMappings extends ANSITerminalTypeMappings { + + public VT220TerminalTypeMappings() { + super(); + + BACKSPACE = 127; + DEL = -1; + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java new file mode 100644 index 0000000..9a83537 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.common.KEYS; + +public class VT320TerminalTypeMappings extends TerminalTypeMappings { + + public VT320TerminalTypeMappings() { + super(); + BACKSPACE = 8; + DEL = 127; + } + + @Override + public void setKeypadMappings() { + escapesToKey.put("[H", KEYS.HOME); + escapesToKey.put("[F", KEYS.END); + escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$ + escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$ + escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$ + escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$ + + } +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java new file mode 100644 index 0000000..8c2b71e --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet.hook; + +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.osgi.baseadaptor.BaseAdaptor; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.virgo.osgi.console.telnet.TelnetManager; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; + +import java.io.IOException; +import java.net.URLConnection; +import java.util.Properties; + +/** + * This adaptor hook starts the telnet implementation on the port, specified by the OSGi property osgi.console. + * Principally, on this port listens the Equinox shell. In order to avoid this, the value of the property (if integer) + * is stored, and the property is removed. In this way, when the Equinox ConsoleManager starts, it cannot retrieve any + * port and does not start a console on it. After that, during the start of the framework, the hook recovers the + * property and starts the telnet on this port. + * <p/> + * It is possible to pass not only the port, but also the host, in order to restrict the opened server socket to a + * particular network address on the host. + */ +public class TelnetHook implements AdaptorHook { + + private boolean consolePortAvailable; + + private String consoleValue; + + private TelnetManager telnetManager = null; + + public void initialize(BaseAdaptor adaptor) { + consoleValue = FrameworkProperties.getProperty(EclipseStarter.PROP_CONSOLE); + if (consoleValue == null) { + return; + } + if (consoleValue.contains(":")) { + consolePortAvailable = true; + FrameworkProperties.getProperties().remove(EclipseStarter.PROP_CONSOLE); + } else { + try { + Integer.parseInt(consoleValue); + consolePortAvailable = true; + FrameworkProperties.getProperties().remove(EclipseStarter.PROP_CONSOLE); + } catch (NumberFormatException ex) { + // do nothing + } + } + } + + public void frameworkStart(BundleContext context) throws BundleException { + if (consolePortAvailable == true) { + FrameworkProperties.setProperty(EclipseStarter.PROP_CONSOLE, String.valueOf(consoleValue)); + telnetManager = new TelnetManager(context); + telnetManager.startConsoleListener(); + } + } + + public void frameworkStop(BundleContext context) throws BundleException { + if (telnetManager != null) { + telnetManager.stop(); + } + } + + public void frameworkStopping(BundleContext context) { + // Do nothing - we don't care about stopping event + } + + public void addProperties(Properties properties) { + // Do nothing - we don't care about addProperties event + } + + public URLConnection mapLocationToURLConnection(String location) throws IOException { + return null; // not supported + } + + public void handleRuntimeError(Throwable error) { + // Do nothing - we don't know how to handle runtime errors + } + + public FrameworkLog createFrameworkLog() { + return null; // not supported + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java new file mode 100644 index 0000000..c64c78d --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet.hook; + +import org.eclipse.osgi.baseadaptor.HookConfigurator; +import org.eclipse.osgi.baseadaptor.HookRegistry; + +import org.eclipse.virgo.osgi.console.telnet.hook.TelnetHook; + +public class TelnetHookConfigurator implements HookConfigurator { + + public void addHooks(HookRegistry hookRegistry) { + hookRegistry.addAdaptorHook(new TelnetHook()); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties b/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties new file mode 100644 index 0000000..afd7017 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties @@ -0,0 +1 @@ +hook.configurators=org.eclipse.virgo.osgi.console.telnet.hook.TelnetHookConfigurator
\ No newline at end of file diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java new file mode 100644 index 0000000..7f63b14 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import org.junit.Assert; +import org.junit.Test; + +public class ConsoleInputStreamTests { + + private static final int DATA_LENGTH = 4; + + @Test + public void addReadBufferTest() throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + byte[] data = new byte[] { (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd' }; + in.add(data); + byte[] read = new byte[DATA_LENGTH]; + for (int i = 0; i < DATA_LENGTH; i++) { + in.read(read, i, 1); + Assert.assertEquals("Incorrect char read; position " + i + " expected: " + data[i] + ", actual: " + read[i], read[i], data[i]); + } + } + + @Test + public void addReadTest() throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + byte[] data = new byte[] { (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd' }; + in.add(data); + for (int i = 0; i < DATA_LENGTH; i++) { + byte symbol = (byte) in.read(); + Assert.assertEquals("Incorrect char read; position " + i + " expected: " + data[i] + ", actual: " + symbol, symbol, data[i]); + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java new file mode 100644 index 0000000..5bbee2f --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; + +public class ConsoleOutputStreamTests { + + private static final int DATA_LENGTH = 4; + + @Test + public void testWrite() throws Exception { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + byte[] data = new byte[] { 'a', 'b', 'c', 'd' }; + for (byte b : data) { + out.write(b); + } + out.flush(); + byte[] res = byteOut.toByteArray(); + + Assert.assertNotNull("Bytes not written; result null", res); + Assert.assertFalse("Bytes not written; result empty", res.length == 0); + + for (int i = 0; i < DATA_LENGTH; i++) { + Assert.assertEquals("Wrong char read. Position " + i + ", expected " + data[i] + ", read " + res[i], data[i], res[i]); + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java new file mode 100644 index 0000000..1633418 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import org.junit.Assert; + +import org.eclipse.virgo.osgi.console.supportability.HistoryHolder; +import org.junit.Test; + +public class HistoryHolderTests { + + @Test + public void test() { + HistoryHolder historyHolder = new HistoryHolder(); + byte[] line1 = new byte[] { 'a', 'b', 'c', 'd' }; + byte[] line2 = new byte[] { 'x', 'y', 'z' }; + byte[] line3 = new byte[] { 'k', 'l', 'm', 'n' }; + + historyHolder.add(line1); + historyHolder.add(line2); + historyHolder.add(line3); + + byte[] first = historyHolder.first(); + Assert.assertEquals("Wrong length of first member", line1.length, first.length); + Assert.assertArrayEquals("Wrong first member", line1, first); + + byte[] last = historyHolder.last(); + Assert.assertEquals("Wrong length of last member", line3.length, last.length); + Assert.assertArrayEquals("Wrong last member", line3, last); + + byte[] prev = historyHolder.prev(); + Assert.assertEquals("Wrong length of previous member", line2.length, prev.length); + Assert.assertArrayEquals("Wrong previous member", line2, prev); + + byte[] next = historyHolder.next(); + Assert.assertEquals("Wrong length of next member", line3.length, next.length); + Assert.assertArrayEquals("Wrong next member", line3, next); + + historyHolder.first(); + historyHolder.add(new byte[] {}); + byte[] current = historyHolder.prev(); + Assert.assertEquals("Wrong length of next member", line3.length, current.length); + Assert.assertArrayEquals("Wrong next member", line3, current); + + historyHolder.first(); + historyHolder.add(line1); + current = historyHolder.prev(); + Assert.assertEquals("Wrong length of next member", line1.length, current.length); + Assert.assertArrayEquals("Wrong next member", line1, current); + Assert.assertArrayEquals("Second line should now be first", line2, historyHolder.first()); + + historyHolder.reset(); + Assert.assertNull("History should be empty", historyHolder.first()); + Assert.assertNull("History should be empty", historyHolder.last()); + Assert.assertNull("History should be empty", historyHolder.next()); + Assert.assertNull("History should be empty", historyHolder.prev()); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java new file mode 100644 index 0000000..0f1c716 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.common; + +import org.junit.Assert; +import org.junit.Test; + +public class SimpleByteBufferTests { + + @Test + public void testBuffer() throws Exception { + SimpleByteBuffer buffer = new SimpleByteBuffer(); + buffer.add('a'); + buffer.add('b'); + buffer.add('c'); + buffer.add('d'); + + Assert.assertTrue("Wrong buffer size; expected 4, actual " + buffer.getSize(), buffer.getSize() == 4); + + check(buffer, new byte[] { 'a', 'b', 'c', 'd' }); + + byte[] data = buffer.getCurrentData(); + byte[] expected = new byte[] { 'a', 'b', 'c', 'd' }; + + Assert.assertTrue("Data not as expected: expected length " + expected.length + ", actual length " + data.length, + data.length == expected.length); + + for (int i = 0; i < data.length; i++) { + Assert.assertEquals("Incorrect data read. Position " + i + ", expected " + expected[i] + ", read " + data[i], expected[i], data[i]); + } + + buffer.insert('a'); + buffer.insert('b'); + buffer.insert('c'); + buffer.insert('d'); + + int pos = buffer.getPos(); + buffer.goLeft(); + int newPos = buffer.getPos(); + Assert.assertEquals("Error while moving left; old pos: " + pos + ", new pos: ", pos - 1, newPos); + + buffer.insert('e'); + check(buffer, new byte[] { 'a', 'b', 'c', 'e', 'd' }); + + buffer.goLeft(); + buffer.delete(); + check(buffer, new byte[] { 'a', 'b', 'c', 'd' }); + + pos = buffer.getPos(); + buffer.goRight(); + newPos = buffer.getPos(); + Assert.assertEquals("Error while moving right; old pos: " + pos + ", new pos: ", pos + 1, newPos); + + buffer.backSpace(); + check(buffer, new byte[] { 'a', 'b', 'c' }); + + buffer.delAll(); + Assert.assertTrue("Bytes in buffer not correctly deleted", (buffer.getSize() == 0) && (buffer.getPos() == 0)); + + buffer.set(new byte[] { 'a', 'b', 'c', 'd' }); + check(buffer, new byte[] { 'a', 'b', 'c', 'd' }); + + data = buffer.copyCurrentData(); + Assert.assertArrayEquals("Buffer copy does not work properly", new byte[] { 'a', 'b', 'c', 'd' }, data); + + buffer.goLeft(); + buffer.replace('e'); + check(buffer, new byte[] { 'a', 'b', 'c', 'e' }); + + buffer.resetPos(); + Assert.assertTrue("Resetting position does not work properly", buffer.getPos() == 0); + + Assert.assertEquals("Wrong current char", 'a', buffer.getCurrentChar()); + } + + private void check(SimpleByteBuffer buffer, byte[] expected) throws Exception { + byte[] data = buffer.copyCurrentData(); + + Assert.assertTrue("Data not as expected: expected length " + expected.length + ", actual length " + data.length, + data.length == expected.length); + + for (int i = 0; i < data.length; i++) { + Assert.assertEquals("Incorrect data read. Position " + i + ", expected " + expected[i] + ", read " + data[i], expected[i], data[i]); + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java new file mode 100644 index 0000000..a320b0a --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.osgi.framework.console.CommandProvider; +import org.junit.Test; +import org.junit.Assert; +import org.eclipse.virgo.teststubs.osgi.framework.StubBundleContext; +import org.eclipse.virgo.teststubs.osgi.support.ObjectClassFilter; + +public class CommandCompleterTests { + + @Test + public void testCompleter() throws Exception { + StubBundleContext context = new StubBundleContext(); + TestCommandProvider commandProvider = new TestCommandProvider(); + context.registerService(CommandProvider.class.getName(), commandProvider, null); + context.addFilter(new ObjectClassFilter(CommandProvider.class.getName())); + CommandCompleter completer = new CommandCompleter(context); + String[] matches = completer.complete("test"); + Set<String> setMatches = convertToSet(matches); + Assert.assertEquals("Incorrect number of matches:", 3, setMatches.size()); + Assert.assertTrue("Matches does not contain test", setMatches.contains("test")); + Assert.assertTrue("Matches does not contain testMethod", setMatches.contains("testMethod")); + Assert.assertTrue("Matches does not contain testMethod1", setMatches.contains("testMethod1")); + + matches = completer.complete("dum"); + setMatches = convertToSet(matches); + Assert.assertEquals("Incorrect number of matches:", 2, setMatches.size()); + Assert.assertTrue("Matches does not contain dummy", setMatches.contains("dummy")); + Assert.assertTrue("Matches does not contain dummyMethod", setMatches.contains("dummyMethod")); + + matches = completer.complete("fake"); + setMatches = convertToSet(matches); + Assert.assertEquals("Incorrect number of matches:", 2, setMatches.size()); + Assert.assertTrue("Matches does not contain fake", setMatches.contains("fake")); + Assert.assertTrue("Matches does not contain fake1", setMatches.contains("fake1")); + + matches = completer.complete("help"); + Assert.assertEquals("Matches should be empty", 0, matches.length); + } + + private Set<String> convertToSet(String[] matches) { + HashSet<String> set = new HashSet<String>(); + for (String match : matches) { + set.add(match); + } + + return set; + } + + class TestCommandProvider implements CommandProvider { + + @Override + public String getHelp() { + return null; + } + + public void _test() { + + } + + public void _testMethod() { + + } + + public void _testMethod1() { + + } + + public void _dummy() { + + } + + public void _dummyMethod() { + + } + + public void _fake() { + + } + + public void _fake1() { + + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java new file mode 100644 index 0000000..80d2042 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +public class ConsoleInputHandlerTests { + + private static final long WAIT_TIME = 10000; + + @Test + public void testHandler() throws Exception { + PipedInputStream input = new PipedInputStream(); + PipedOutputStream output = new PipedOutputStream(input); + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleInputHandler handler = new ConsoleInputHandler(input, in, byteOut, null); + byte[] expected = new byte[] { 'a', 'b', 'c', 'd', 'e', '\r', '\n' }; + output.write(expected); + output.flush(); + handler.start(); + + try { + Thread.sleep(WAIT_TIME); + } catch (Exception e) { + // do nothing + } + + byte[] read = new byte[expected.length]; + for (int i = 0; i < expected.length; i++) { + in.read(read, i, 4); + Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]); + } + + output.close(); + input.close(); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java new file mode 100644 index 0000000..a9d5c7d --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import org.eclipse.osgi.framework.console.CommandProvider; +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.eclipse.virgo.osgi.console.common.KEYS; +import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.SCOTerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT100TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT220TerminalTypeMappings; +import org.eclipse.virgo.osgi.console.telnet.VT320TerminalTypeMappings; +import org.eclipse.virgo.teststubs.osgi.framework.StubBundleContext; +import org.eclipse.virgo.teststubs.osgi.support.ObjectClassFilter; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class ConsoleInputScannerTests { + + private static int BS; + + private static final int LF = 10; + + private static final int CR = 13; + + private static final int ESC = 27; + + private static int DELL; + + @Test + public void test() throws Exception { + Set<TerminalTypeMappings> supportedEscapeSequences = new HashSet<TerminalTypeMappings>(); + supportedEscapeSequences.add(new ANSITerminalTypeMappings()); + supportedEscapeSequences.add(new VT100TerminalTypeMappings()); + supportedEscapeSequences.add(new VT220TerminalTypeMappings()); + supportedEscapeSequences.add(new VT320TerminalTypeMappings()); + supportedEscapeSequences.add(new SCOTerminalTypeMappings()); + + for (TerminalTypeMappings ttMappings : supportedEscapeSequences) { + Map<String, KEYS> escapesToKey = ttMappings.getEscapesToKey(); + Map<KEYS, byte[]> keysToEscapes = new HashMap<KEYS, byte[]>(); + for (Entry<String, KEYS> entry : escapesToKey.entrySet()) { + keysToEscapes.put(entry.getValue(), entry.getKey().getBytes()); + } + + BS = ttMappings.getBackspace(); + DELL = ttMappings.getDel(); + + testScan(ttMappings, keysToEscapes); + } + } + + private void testScan(TerminalTypeMappings mappings, Map<KEYS, byte[]> keysToEscapes) throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + StubBundleContext context = new StubBundleContext(); + TestCommandProvider commandProvider = new TestCommandProvider(); + context.registerService(CommandProvider.class.getName(), commandProvider, null); + context.addFilter(new ObjectClassFilter(CommandProvider.class.getName())); + ConsoleInputScanner scanner = new ConsoleInputScanner(in, out, context); + scanner.setBackspace(mappings.getBackspace()); + scanner.setCurrentEscapesToKey(mappings.getEscapesToKey()); + scanner.setDel(mappings.getDel()); + scanner.setEscapes(mappings.getEscapes()); + + byte[] line1 = new byte[] { 'a', 'b', 'c', 'd', 'e' }; + byte[] line2 = new byte[] { 't', 'e', 's', 't' }; + byte[] line3 = new byte[] { 'l', 'a', 's', 't' }; + + addLine(scanner, line1); + checkInpusStream(in, line1); + + addLine(scanner, line2); + checkInpusStream(in, line2); + + addLine(scanner, line3); + checkInpusStream(in, line3); + + add(scanner, keysToEscapes.get(KEYS.UP)); + add(scanner, keysToEscapes.get(KEYS.UP)); + String res = byteOut.toString(); + Assert.assertTrue("Error processing up arrow; expected test, actual " + res.substring(res.length() - 4), res.endsWith("test")); + + add(scanner, keysToEscapes.get(KEYS.DOWN)); + res = byteOut.toString(); + Assert.assertTrue("Error processing down arrow; expected last, actual " + res.substring(res.length() - 4), res.endsWith("last")); + + add(scanner, keysToEscapes.get(KEYS.PGUP)); + res = byteOut.toString(); + Assert.assertTrue("Error processing PageUp; expected abcde, actual " + res.substring(res.length() - 4), res.endsWith("abcde")); + + add(scanner, keysToEscapes.get(KEYS.PGDN)); + res = byteOut.toString(); + Assert.assertTrue("Error processing PageDown; expected last, actual " + res.substring(res.length() - 4), res.endsWith("last")); + + if (BS > 0) { + scanner.scan(BS); + res = byteOut.toString(); + Assert.assertTrue("Error processing backspace; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las")); + scanner.scan('t'); + } + + if (DELL > 0) { + add(scanner, keysToEscapes.get(KEYS.LEFT)); + scanner.scan(DELL); + res = byteOut.toString(); + Assert.assertTrue("Error processing del; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las")); + scanner.scan('t'); + } + + add(scanner, keysToEscapes.get(KEYS.LEFT)); + add(scanner, keysToEscapes.get(KEYS.LEFT)); + add(scanner, keysToEscapes.get(KEYS.RIGHT)); + if (DELL > 0) { + scanner.scan(DELL); + } else { + add(scanner, keysToEscapes.get(KEYS.DEL)); + } + res = byteOut.toString(); + Assert.assertTrue("Error processing arrows; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las")); + scanner.scan('t'); + + if (keysToEscapes.get(KEYS.DEL) != null) { + add(scanner, keysToEscapes.get(KEYS.LEFT)); + add(scanner, keysToEscapes.get(KEYS.DEL)); + res = byteOut.toString(); + Assert.assertTrue("Error processing delete; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las")); + scanner.scan('t'); + } + + add(scanner, keysToEscapes.get(KEYS.HOME)); + if (DELL > 0) { + scanner.scan(DELL); + } else { + add(scanner, keysToEscapes.get(KEYS.DEL)); + } + res = byteOut.toString(); + res = res.substring(res.length() - 6, res.length() - 3); + Assert.assertTrue("Error processing Home; expected ast, actual " + res, res.equals("ast")); + scanner.scan('l'); + + add(scanner, keysToEscapes.get(KEYS.END)); + add(scanner, keysToEscapes.get(KEYS.LEFT)); + if (DELL > 0) { + scanner.scan(DELL); + } else { + add(scanner, keysToEscapes.get(KEYS.DEL)); + } + res = byteOut.toString(); + Assert.assertTrue("Error processing End; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las")); + scanner.scan('t'); + + add(scanner, keysToEscapes.get(KEYS.LEFT)); + add(scanner, keysToEscapes.get(KEYS.INS)); + scanner.scan('a'); + res = byteOut.toString(); + Assert.assertTrue("Error processing Ins; expected las, actual " + res.substring(res.length() - 4), res.endsWith("lasa")); + + scanner.scan(CR); + scanner.scan(LF); + scanner.scan('t'); + scanner.scan('e'); + scanner.scan(9); + res = byteOut.toString(); + Assert.assertTrue("Expected completion suggestions are not contained in the output", res.contains("test testMethod")); + } + + private static void addLine(ConsoleInputScanner scanner, byte[] line) throws Exception { + for (byte b : line) { + try { + scanner.scan(b); + } catch (Exception e) { + System.out.println("Error scanning symbol " + b); + throw new Exception("Error scanning symbol" + b); + } + } + + try { + scanner.scan(CR); + } catch (Exception e) { + System.out.println("Error scanning symbol " + CR); + throw new Exception("Error scanning symbol " + CR); + } + + try { + scanner.scan(LF); + } catch (Exception e) { + System.out.println("Error scanning symbol " + LF); + throw new Exception("Error scanning symbol " + LF); + } + } + + private void add(ConsoleInputScanner scanner, byte[] sequence) throws Exception { + scanner.scan(ESC); + for (byte b : sequence) { + scanner.scan(b); + } + } + + private void checkInpusStream(ConsoleInputStream in, byte[] expected) throws Exception { + // the actual number of bytes in the stream is two more than the bytes in the array, because of the CR and LF + // symbols, added after the array + byte[] read = new byte[expected.length + 2]; + for (int i = 0; i < expected.length; i++) { + in.read(read, i, 1); + Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]); + } + in.read(read, expected.length, 1); + in.read(read, expected.length + 1, 1); + } + + class TestCommandProvider implements CommandProvider { + + @Override + public String getHelp() { + // TODO Auto-generated method stub + return null; + } + + public void _test() { + + } + + public void _testMethod() { + + } + + public void _dummy() { + + } + + public void _fake() { + + } + + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java new file mode 100644 index 0000000..0cbbfe3 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.supportability; + +import java.io.ByteArrayOutputStream; + +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.junit.Assert; +import org.junit.Test; + +public class GrepTests { + + private static final int CR = 13; + + private static final int LF = 10; + + @Test + public void testGrep() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + ConsoleOutputStream consoleOut = new ConsoleOutputStream(output); + byte[] expression = "test".getBytes(); + Grep grep = new Grep(expression, consoleOut); + grep.start(); + byte[] line1 = new byte[] { 't', 'e', 's', 't' }; + byte[] line2 = new byte[] { 'a', 'b', 'c', 'd' }; + byte[] line3 = new byte[] { 't', 'e', 's', 't', 'e', 'r' }; + byte[] line4 = new byte[] { 'k', 'l', 'm', 'n' }; + byte[] line5 = new byte[] { 't', 'e', 's', 't', 'i', 'n', 'g' }; + byte[] line6 = new byte[] { 'o', 's', 'g', 'i', '>' }; + + addLine(consoleOut, line1); + addLine(consoleOut, line2); + addLine(consoleOut, line3); + addLine(consoleOut, line4); + addLine(consoleOut, line5); + + for (byte b : line6) { + consoleOut.write(b); + } + consoleOut.flush(); + + grep.join(); + + byte[] expectedResult = new byte[] { 't', 'e', 's', 't', CR, LF, 't', 'e', 's', 't', 'e', 'r', CR, LF, 't', 'e', 's', 't', 'i', 'n', 'g', CR, + LF, 'o', 's', 'g', 'i', '>', ' ' }; + byte[] result = output.toByteArray(); + + Assert.assertNotNull("Bytes not written; result null", result); + Assert.assertFalse("Bytes not written; result empty", result.length == 0); + Assert.assertTrue("Bytes written to output differ in number from expected", result.length == expectedResult.length); + + for (int i = 0; i < result.length; i++) { + Assert.assertEquals("Wrong char read. Position " + i + ", expected " + expectedResult[i] + ", read " + result[i], expectedResult[i], + result[i]); + } + } + + private static void addLine(ConsoleOutputStream out, byte[] line) throws Exception { + for (byte b : line) { + try { + out.write(b); + } catch (Exception e) { + System.out.println("Error writing symbol " + b); + throw new Exception("Error writing symbol" + b); + } + } + + try { + out.write(CR); + } catch (Exception e) { + System.out.println("Error writing symbol " + CR); + throw new Exception("Error writing symbol " + CR); + } + + try { + out.write(LF); + } catch (Exception e) { + System.out.println("Error writing symbol " + LF); + throw new Exception("Error writing symbol " + LF); + } + + out.flush(); + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java new file mode 100644 index 0000000..c065840 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import java.net.ServerSocket; +import java.net.Socket; + +import org.junit.Assert; +import org.junit.Test; + +public class NegotiationFinishedCallbackTests { + + @Test + public void finishTest() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + TelnetConsoleSession consoleSession = null; + try { + servSocket = new ServerSocket(0); + socketClient = new Socket("localhost", servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + consoleSession = new TelnetConsoleSession(socketServer, null); + NegotiationFinishedCallback callback = new NegotiationFinishedCallback(consoleSession); + callback.finished(); + Assert.assertTrue("Finished not called on console session", consoleSession.isTelnetNegotiationFinished); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (consoleSession != null) { + consoleSession.doClose(); + } + + try { + if (socketServer != null) { + Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed()); + } + } finally { + if (servSocket != null) { + servSocket.close(); + } + } + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java new file mode 100644 index 0000000..92569b8 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; + +public class TelnetConsoleSessionTests { + + private static final int TEST_CONTENT = 100; + + private static final int IAC = 255; + + @Test + public void testConsoleSession() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + TelnetConsoleSession consoleServer = null; + OutputStream outClient = null; + OutputStream outServer = null; + + try { + servSocket = new ServerSocket(0); + socketClient = new Socket("localhost", servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + consoleServer = new TelnetConsoleSession(socketServer, null); + consoleServer.start(); + + outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + + InputStream input = consoleServer.getInput(); + int in = input.read(); + Assert.assertTrue("Server received [" + in + "] instead of " + TEST_CONTENT + " from the telnet client.", in == TEST_CONTENT); + + input = socketClient.getInputStream(); + in = input.read(); + // here IAC is expected, since when the output stream in TelnetConsoleSession is created, several telnet + // commands are written to it, each of them starting with IAC + Assert.assertTrue("Client receive telnet responses from the server unexpected value [" + in + "] instead of " + IAC + ".", in == IAC); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (consoleServer != null) { + consoleServer.doClose(); + } + if (outClient != null) { + outClient.close(); + } + if (outServer != null) { + outServer.close(); + } + + try { + if (socketServer != null) { + Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed()); + } + } finally { + if (servSocket != null) { + servSocket.close(); + } + } + } + } + + @Test + public void testConsoleSessionVoidWrapper() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + TelnetConsoleSession consoleServer = null; + + try { + servSocket = new ServerSocket(0); + socketClient = new Socket("localhost", servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + consoleServer = new TelnetConsoleSession(socketServer, null); + consoleServer.start(); + + OutputStream outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + + InputStream input = consoleServer.getInput(); + int in = input.read(); + + Assert.assertTrue("Server received [" + in + "] instead of " + TEST_CONTENT + " from the telnet client.", in == TEST_CONTENT); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (consoleServer != null) { + consoleServer.doClose(); + } + + try { + if (socketServer != null) { + Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed()); + } + } finally { + if (servSocket != null) { + servSocket.close(); + } + } + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java new file mode 100644 index 0000000..d89fec1 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.junit.Assert; +import org.junit.Test; + +import static org.easymock.EasyMock.*; + +import java.io.ByteArrayOutputStream; +import java.io.StringBufferInputStream; + +public class TelnetInputHandlerTests { + + private static final long WAIT_TIME = 10000; + + @Test + public void testHandler() throws Exception { + StringBufferInputStream input = new StringBufferInputStream("abcde"); + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + Callback callback = createMock(Callback.class); + TelnetInputHandler handler = new TelnetInputHandler(input, in, out, callback); + handler.start(); + + // wait for the accept thread to start execution + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + + String res = byteOut.toString(); + Assert.assertTrue("Wrong input. Expected abcde, read " + res, res.equals("abcde")); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java new file mode 100644 index 0000000..feda304 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.eclipse.virgo.osgi.console.common.ConsoleInputStream; +import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream; +import org.eclipse.virgo.osgi.console.common.KEYS; +import org.junit.Assert; +import org.junit.Test; + +import static org.easymock.EasyMock.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.HashMap; +import java.util.Map; + +public class TelnetInputScannerTests { + + private static final int IAC = 255; + + private static final int DO = 253; + + private static final int TTYPE = 24; + + private static final int WILL = 251; + + private static final int SB = 250; + + private static final int SE = 240; + + private static final int SEND = 1; + + private static final int IS = 0; + + protected static final byte ESC = 27; + + @Test + public void testScan() throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + Callback callback = createMock(Callback.class); + TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback); + try { + scanner.scan((byte) 240); + scanner.scan((byte) 248); + scanner.scan((byte) 250); + scanner.scan((byte) 251); + scanner.scan((byte) 252); + scanner.scan((byte) 253); + scanner.scan((byte) 254); + scanner.scan((byte) 'a'); + scanner.scan((byte) 'b'); + scanner.scan((byte) 'c'); + } catch (IOException e) { + System.out.println("Error while scanning: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + + String output = byteOut.toString(); + Assert.assertTrue("Output incorrect. Expected abc, but read " + output, output.equals("abc")); + } + + @Test + public void testScanESC() throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + Callback callback = createMock(Callback.class); + TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback); + + try { + scanner.scan((byte) 'a'); + scanner.scan((byte) ESC); + scanner.scan((byte) 'b'); + } catch (IOException e) { + System.out.println("Error while scanning: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + + String output = byteOut.toString(); + Assert.assertTrue("Output incorrect. Expected abc, but read " + output, output.equals("ab")); + } + + @Test + public void testTTNegotiations() throws Exception { + Map<byte[], TerminalTypeMappings> ttMappings = new HashMap<byte[], TerminalTypeMappings>(); + ttMappings.put(new byte[] { 'A', 'N', 'S', 'I' }, new ANSITerminalTypeMappings()); + ttMappings.put(new byte[] { 'V', 'T', '1', '0', '0' }, new VT100TerminalTypeMappings()); + ttMappings.put(new byte[] { 'V', 'T', '2', '2', '0' }, new VT220TerminalTypeMappings()); + ttMappings.put(new byte[] { 'X', 'T', 'E', 'R', 'M' }, new VT220TerminalTypeMappings()); + ttMappings.put(new byte[] { 'V', 'T', '3', '2', '0' }, new VT320TerminalTypeMappings()); + ttMappings.put(new byte[] { 'S', 'C', 'O' }, new SCOTerminalTypeMappings()); + + for (byte[] ttype : ttMappings.keySet()) { + testTerminalTypeNegotiation(ttype, ttMappings.get(ttype)); + } + } + + private void testTerminalTypeNegotiation(byte[] terminalType, TerminalTypeMappings mappings) throws Exception { + PipedInputStream clientIn = new PipedInputStream(); + PipedOutputStream serverOut = new PipedOutputStream(clientIn); + + byte[] requestNegotiation = { (byte) IAC, (byte) DO, (byte) TTYPE }; + + TestCallback testCallback = new TestCallback(); + TelnetOutputStream out = new TelnetOutputStream(serverOut); + TelnetInputScanner scanner = new TelnetInputScanner(new ConsoleInputStream(), out, testCallback); + out.write(requestNegotiation); + out.flush(); + + int read = clientIn.read(); + Assert.assertEquals("Unexpected input ", IAC, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", DO, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", TTYPE, read); + + scanner.scan(IAC); + scanner.scan(WILL); + scanner.scan(TTYPE); + + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", IAC, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SB, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", TTYPE, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SEND, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", IAC, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SE, read); + + scanner.scan(IAC); + scanner.scan(SB); + scanner.scan(TTYPE); + scanner.scan(IS); + scanner.scan('A'); + scanner.scan('B'); + scanner.scan('C'); + scanner.scan('D'); + scanner.scan('E'); + scanner.scan(IAC); + scanner.scan(SE); + + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", IAC, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SB, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", TTYPE, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SEND, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", IAC, read); + read = clientIn.read(); + Assert.assertEquals("Unexpected input ", SE, read); + + scanner.scan(IAC); + scanner.scan(SB); + scanner.scan(TTYPE); + scanner.scan(IS); + for (byte symbol : terminalType) { + scanner.scan(symbol); + } + scanner.scan(IAC); + scanner.scan(SE); + + Assert.assertEquals("Incorrect BACKSPACE: ", mappings.getBackspace(), scanner.getBackspace()); + Assert.assertEquals("Incorrect DELL: ", mappings.getDel(), scanner.getDel()); + + Map<String, KEYS> currentEscapesToKey = scanner.getCurrentEscapesToKey(); + Map<String, KEYS> expectedEscapesToKey = mappings.getEscapesToKey(); + for (String escape : expectedEscapesToKey.keySet()) { + KEYS key = expectedEscapesToKey.get(escape); + Assert.assertEquals("Incorrect " + key.name(), key, currentEscapesToKey.get(escape)); + } + + Assert.assertTrue("Callback not called ", testCallback.getState()); + } + + class TestCallback implements Callback { + + private boolean isCalled = false; + + @Override + public void finished() { + isCalled = true; + } + + public boolean getState() { + return isCalled; + } + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java new file mode 100644 index 0000000..cf46a24 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2010 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.junit.Assert; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.Socket; + +import static org.easymock.EasyMock.*; + +public class TelnetManagerTests { + + private static final String PROP_CONSOLE = "osgi.console"; + + private static final String PROP_CONSOLE_VALUE_POSITIVE = "localhost:38888"; + + private static final String PROP_CONSOLE_VALUE_NEGATIVE = "localhost38889"; + + private static final int PORT_POSITIVE_TEST = 38888; + + private static final int PORT_NEGATIVE_TEST = 38889; + + private static final long WAIT_TIME = 5000; + + private static final int TEST_CONTENT = 100; + + @Test + public void testTelnetManager() throws Exception { + + BundleContext bundleContext = createMock(BundleContext.class); + + expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(PROP_CONSOLE_VALUE_POSITIVE); + + replay(bundleContext); + + TelnetManager telnetMngr = new TelnetManager(bundleContext); + telnetMngr.startConsoleListener(); + Socket socketClient = null; + + try { + socketClient = new Socket("localhost", PORT_POSITIVE_TEST); + OutputStream outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + + // wait for the accept thread to finish execution + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + } finally { + if (socketClient != null) { + socketClient.close(); + } + telnetMngr.stop(); + } + + verify(bundleContext); + } + + @Test + public void testTelnetManagerNegative() throws Exception { + BundleContext bundleContext = createMock(BundleContext.class); + + expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(PROP_CONSOLE_VALUE_NEGATIVE); + + replay(bundleContext); + + TelnetManager telnetMngr = new TelnetManager(bundleContext); + telnetMngr.startConsoleListener(); + + boolean serverSocketNotCreated = false; + try { + Socket socketClient = new Socket("localhost", PORT_NEGATIVE_TEST); + socketClient.close(); + } catch (ConnectException ec) { + serverSocketNotCreated = true; + } + + telnetMngr.stop(); + verify(bundleContext); + + Assert.assertTrue("Telnet console socket getter is created on localhost", serverSocketNotCreated); + } + + @Test + public void testTelnetManagerWithoutHost() throws Exception { + BundleContext bundleContext = createMock(BundleContext.class); + + expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(String.valueOf(PORT_POSITIVE_TEST)); + + replay(bundleContext); + + TelnetManager telnetMngr = new TelnetManager(bundleContext); + telnetMngr.startConsoleListener(); + Socket socketClient = null; + + try { + socketClient = new Socket("localhost", PORT_POSITIVE_TEST); + OutputStream outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + + // wait for the accept thread to finish execution + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + } finally { + if (socketClient != null) { + socketClient.close(); + } + telnetMngr.stop(); + } + + verify(bundleContext); + } + +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java new file mode 100644 index 0000000..7753e4a --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; + +public class TelnetOutputStreamTests { + + @Test + public void testAutoSend() throws Exception { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + TelnetOutputStream out = new TelnetOutputStream(byteOut); + out.autoSend(); + out.flush(); + byte[] message = byteOut.toByteArray(); + + Assert.assertNotNull("Auto message not sent", message); + Assert.assertFalse("Auto message not sent", message.length == 0); + Assert.assertTrue("Error sending auto message. Expected length: " + TelnetOutputStream.autoMessage.length + ", actual length: " + + message.length, message.length == TelnetOutputStream.autoMessage.length); + + for (int i = 0; i < message.length; i++) { + Assert.assertEquals("Wrong char in auto message. Position: " + i + ", expected: " + TelnetOutputStream.autoMessage[i] + ", read: " + + message[i], TelnetOutputStream.autoMessage[i], message[i]); + } + } +} diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java new file mode 100644 index 0000000..5d53d2f --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2011 SAP AG + * 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: + * Lazar Kirchev, SAP AG - initial contribution + ******************************************************************************/ + +package org.eclipse.virgo.osgi.console.telnet.hook; + +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.Socket; + +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.osgi.baseadaptor.BaseAdaptor; +import org.junit.Assert; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +import static org.easymock.EasyMock.*; + +public class TelnetHookTests { + + @Test + public void testHostAndPort() throws Exception { + System.setProperty(EclipseStarter.PROP_CONSOLE, "localhost:3333"); + System.setProperty("osgi.configuration.area", "."); + BaseAdaptor adaptor = new BaseAdaptor(null); + TelnetHook telnetHook = new TelnetHook(); + telnetHook.initialize(adaptor); + String consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE); + Assert.assertNull("Console port should not be present in the properties", consoleProp); + + BundleContext bundleContext = createMock(BundleContext.class); + + expect(bundleContext.getProperty(EclipseStarter.PROP_CONSOLE)).andReturn("localhost:3333"); + replay(bundleContext); + + telnetHook.frameworkStart(bundleContext); + + consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE); + Assert.assertEquals("Console port should not be present in the properties", "localhost:3333", consoleProp); + + telnetHook.frameworkStop(bundleContext); + + telnetHook.frameworkStopping(bundleContext); + telnetHook.addProperties(null); + telnetHook.mapLocationToURLConnection(null); + telnetHook.handleRuntimeError(null); + Assert.assertNull(telnetHook.createFrameworkLog()); + + Socket socketClient = null; + try { + socketClient = new Socket("localhost", 3333); + OutputStream outClient = socketClient.getOutputStream(); + outClient.write(100); + outClient.write('\n'); + outClient.flush(); + Assert.fail("Server socket should be closed, no connection should be established"); + } catch (ConnectException ex) { + // this is expected + } finally { + if (socketClient != null) { + socketClient.close(); + } + } + + verify(bundleContext); + } + + @Test + public void testPort() { + BaseAdaptor adaptor = new BaseAdaptor(null); + TelnetHook telnetHook = new TelnetHook(); + System.setProperty(EclipseStarter.PROP_CONSOLE, "3333"); + telnetHook.initialize(adaptor); + String consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE); + Assert.assertNull("Console port should not be present in the properties", consoleProp); + } +} diff --git a/org.eclipse.virgo.osgi.console/template.mf b/org.eclipse.virgo.osgi.console/template.mf new file mode 100644 index 0000000..adf77b4 --- a/dev/null +++ b/org.eclipse.virgo.osgi.console/template.mf @@ -0,0 +1,19 @@ +Bundle-ManifestVersion: 2 +Bundle-Name: Console +Bundle-Vendor: SpringSource Inc. +Bundle-SymbolicName: org.eclipse.virgo.osgi.console +Bundle-Version: 0 +Fragment-Host: org.eclipse.osgi; extension:=framework +Excluded-Exports: + *.internal.*, + org.eclipse.core.*, + org.eclipse.osgi.*, + org.osgi.framework.*, + org.osgi.service.* +Excluded-Imports: + org.eclipse.core.*;version="0", + org.eclipse.osgi.*;version="0", + org.osgi.framework.*;version="0", + org.osgi.service.*;version="0", + org.osgi.util.*;version="0" + |

