diff options
116 files changed, 12654 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.console.jaas.fragment/.classpath b/bundles/org.eclipse.equinox.console.jaas.fragment/.classpath new file mode 100755 index 000000000..bc74aabe3 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/.classpath @@ -0,0 +1,6 @@ +<?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="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.console.jaas.fragment/.project b/bundles/org.eclipse.equinox.console.jaas.fragment/.project new file mode 100755 index 000000000..a6719df74 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.equinox.console.jaas.fragment</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/bundles/org.eclipse.equinox.console.jaas.fragment/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.console.jaas.fragment/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 000000000..b47753e57 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Sat May 28 14:11:27 EEST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.equinox.console.jaas.fragment/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.console.jaas.fragment/META-INF/MANIFEST.MF new file mode 100755 index 000000000..ee3f2e185 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: SSHD Fragment +Bundle-SymbolicName: org.eclipse.equinox.console.jaas.fragment +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +DynamicImport-Package: org.eclipse.equinox.console.jaas +Fragment-Host: org.apache.sshd.core;bundle-version="0.5.0" diff --git a/bundles/org.eclipse.equinox.console.jaas.fragment/about.html b/bundles/org.eclipse.equinox.console.jaas.fragment/about.html new file mode 100755 index 000000000..bed8451cf --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/about.html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> +<title>About</title> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>May 28, 2011</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.console.jaas.fragment/build.properties b/bundles/org.eclipse.equinox.console.jaas.fragment/build.properties new file mode 100755 index 000000000..a854d1dc8 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.jaas.fragment/build.properties @@ -0,0 +1,2 @@ +bin.includes = META-INF/,\ + about.html diff --git a/bundles/org.eclipse.equinox.console.tests/.classpath b/bundles/org.eclipse.equinox.console.tests/.classpath new file mode 100755 index 000000000..bb16802ac --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/.classpath @@ -0,0 +1,11 @@ +<?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="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> + <classpathentry kind="lib" path="C:/Users/i043832/workspace_console/log4j-1.2.13.jar"/> + <classpathentry kind="lib" path="C:/Users/i043832/workspace_console/slf4j-api-1.5.11.jar"/> + <classpathentry kind="lib" path="C:/Users/i043832/workspace_console/slf4j-log4j12-1.5.11.jar"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.console.tests/.gitignore b/bundles/org.eclipse.equinox.console.tests/.gitignore new file mode 100755 index 000000000..3b8360084 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/.gitignore @@ -0,0 +1 @@ +/hostkey.ser diff --git a/bundles/org.eclipse.equinox.console.tests/.project b/bundles/org.eclipse.equinox.console.tests/.project new file mode 100755 index 000000000..7d67a8eb6 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.equinox.console.tests</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/bundles/org.eclipse.equinox.console.tests/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.console.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 000000000..507d6a6a1 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Tue Apr 12 16:03:12 EEST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.equinox.console.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.console.tests/META-INF/MANIFEST.MF new file mode 100755 index 000000000..04a2c18fc --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Console supportability tests +Bundle-SymbolicName: org.eclipse.equinox.console.tests +Bundle-Version: 1.0.0.qualifier +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Import-Package: junit.framework;version="4.8.1", + org.apache.sshd.client.future, + org.easymock;version="2.4.0", + org.junit;version="4.8.1" +Fragment-Host: org.eclipse.equinox.console diff --git a/bundles/org.eclipse.equinox.console.tests/about.html b/bundles/org.eclipse.equinox.console.tests/about.html new file mode 100755 index 000000000..bed8451cf --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/about.html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> +<title>About</title> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>May 28, 2011</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.console.tests/build.properties b/bundles/org.eclipse.equinox.console.tests/build.properties new file mode 100755 index 000000000..34d2e4d2d --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/Activator.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/Activator.java new file mode 100755 index 000000000..e024b14fa --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/Activator.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator implements BundleActivator { + private static BundleContext context; + + public void start(BundleContext context) throws Exception { + this.context = context; + } + + + public void stop(BundleContext context) throws Exception { + this.context = null; + } + + public static BundleContext getContext() { + return context; + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/ActivatorTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/ActivatorTests.java new file mode 100755 index 000000000..d1c3ab37b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/ActivatorTests.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.command.adapter; + +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.junit.Test; + +public class ActivatorTests { + + private static final String SCOPE_PROPERTY_NAME = "osgi.command.scope"; + private static final String FUNCTION_PROPERTY_NAME = "osgi.command.function"; + private static final String EQUINOX_SCOPE = "equinox"; + + @Test + public void testGetCommandMethods() { + Set<String> commandNames = new HashSet<String>(); + commandNames.add("_testMethod1"); + commandNames.add("_testMethod2"); + commandNames.add("_testMethod3"); + + Activator activator = new Activator(); + CommandProvider command = new TestCommandProvider(); + Method[] methods = activator.getCommandMethods(command); + + assertEquals("Command methods not as expected", 3, methods.length); + for (Method method : methods) { + assertTrue("Command methods should not include " + method.getName(), commandNames.contains(method.getName())); + } + + Dictionary<String, Object> props = activator.getAttributes(methods); + assertTrue("Attributes should contain property " + SCOPE_PROPERTY_NAME + " with value " + EQUINOX_SCOPE, EQUINOX_SCOPE.equals(props.get(SCOPE_PROPERTY_NAME))); + String[] methodNames = (String[])props.get(FUNCTION_PROPERTY_NAME); + assertEquals("Methods number not as expected", methods.length, methodNames.length); + + for(int i = 0; i < methods.length; i++) { + assertEquals("Wrong method name", methods[i].getName().substring(1), methodNames[i]); + } + } + + class TestCommandProvider implements CommandProvider { + public void _testMethod1(CommandInterpreter i) { + + } + + public void _testMethod2(CommandInterpreter i) { + + } + + public void _testMethod3(CommandInterpreter i) { + + } + + private void _method(CommandInterpreter i) { + + } + + @Override + public String getHelp() { + // TODO Auto-generated method stub + return null; + } + + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapterTest.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapterTest.java new file mode 100755 index 000000000..b9c6c3f15 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapterTest.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.command.adapter; + +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.junit.Test; + +public class CommandProviderAdapterTest { + + @Test + public void testMain() throws Exception { + CommandProvider provider = new TestCommandProvider(); + Method[] methods = TestCommandProvider.class.getMethods(); + Set<Method> m = new HashSet<Method>(); + for (Method method : methods) { + if (method.getName().startsWith("_")) { + m.add(method); + } + } + CommandProviderAdapter providerAdapter = new CommandProviderAdapter(provider, m.toArray(new Method[0])); + + String result = (String) providerAdapter.main(new Object[] {"test"}); + assertEquals("Result should be test", "test", result); + + result = (String) providerAdapter.main(new Object[] {"echo", "hello"}); + assertEquals("Result should be hello", "hello", result); + } + + class TestCommandProvider implements CommandProvider { + public String _test(CommandInterpreter i) { + return "test"; + } + + public String _echo(CommandInterpreter i) { + return i.nextArgument(); + } + + @Override + public String getHelp() { + return "this is a test command provider"; + } + + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/ConsoleInputStreamTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/ConsoleInputStreamTests.java new file mode 100755 index 000000000..b3732bbfc --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/ConsoleInputStreamTests.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.equinox.console.common; + +import org.junit.Assert; +import org.junit.Test; + +import org.eclipse.equinox.console.common.ConsoleInputStream; + +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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/ConsoleOutputStreamTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/ConsoleOutputStreamTests.java new file mode 100755 index 000000000..c1044ee8b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/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.equinox.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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/HistoryHolderTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/HistoryHolderTests.java new file mode 100755 index 000000000..c6321a63b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/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.equinox.console.common; + +import org.junit.Assert; +import org.junit.Test; + +import org.eclipse.equinox.console.supportability.HistoryHolder; + +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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/SimpleByteBufferTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/common/SimpleByteBufferTests.java new file mode 100755 index 000000000..9d4362431 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/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.equinox.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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandLineParserTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandLineParserTests.java new file mode 100755 index 000000000..408c8a8f1 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandLineParserTests.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class CommandLineParserTests { + + private static final String PIPE_TEST_INPUT = "command1|comm"; + private static final String CONSECUTIVE_COMMANDS_TEST_INPUT = "command1;comm"; + private static final String ASSIGNMENT_TEST_INPUT = "var=val"; + private static final String START_CLOSURE_TEST_INPUT = "${comm"; + private static final String END_CLOSURE_TEST_INPUT = "${command}arg1"; + private static final String START_MACRO_TEST_INPUT = "$(macr"; + private static final String END_MACRO_TEST_INPUT = "$(macro)val"; + private static final String VARIABLE_TEST_INPUT = "$VAR"; + private static final String START_MAP_TEST_INPUT = "<key=val"; + private static final String END_MAP_TEST_INPUT = "<key=val>other"; + private static final String START_LIST_TEST_INPUT = "[elem1,elem2,el"; + private static final String LIST_TEST_INPUT = "[elem1, elem2, elem3]other"; + private static final String COMMAND_ARGUMENTS_TEST_INPUT = "command argument1 argum"; + private static final String COMMAND_NAME_TEST_INPUT = "com"; + private static final String COMMENT_TEST_INPUT="command#comment"; + + @Test + public void testGetCurrentToken() { + String token; + + token = CommandLineParser.getCurrentToken(PIPE_TEST_INPUT, PIPE_TEST_INPUT.length()); + assertEquals("Pipe not parsed correctly", "comm", token); + + token = CommandLineParser.getCurrentToken(CONSECUTIVE_COMMANDS_TEST_INPUT, CONSECUTIVE_COMMANDS_TEST_INPUT.length()); + assertEquals("Consequtive commands not parsed correctly", "comm", token); + + token = CommandLineParser.getCurrentToken(ASSIGNMENT_TEST_INPUT, ASSIGNMENT_TEST_INPUT.length()); + assertEquals("Assignment not parsed correctly", "val", token); + + token = CommandLineParser.getCurrentToken(START_CLOSURE_TEST_INPUT, START_CLOSURE_TEST_INPUT.length()); + assertEquals("Start closure not parsed correctly", "comm", token); + + token = CommandLineParser.getCurrentToken(END_CLOSURE_TEST_INPUT, END_CLOSURE_TEST_INPUT.length()); + assertEquals("End closure not parsed correctly", "arg1", token); + + token = CommandLineParser.getCurrentToken(START_MACRO_TEST_INPUT, START_MACRO_TEST_INPUT.length()); + assertEquals("Start macro not parsed correctly", "macr", token); + + token = CommandLineParser.getCurrentToken(END_MACRO_TEST_INPUT, END_MACRO_TEST_INPUT.length()); + assertEquals("End macro not parsed correctly", "val", token); + + token = CommandLineParser.getCurrentToken(VARIABLE_TEST_INPUT, VARIABLE_TEST_INPUT.length()); + assertEquals("Variable name not parsed correctly", "VAR", token); + + token = CommandLineParser.getCurrentToken(START_MAP_TEST_INPUT, START_MAP_TEST_INPUT.length()); + assertNull("Start map not parsed correctly", token); + + token = CommandLineParser.getCurrentToken(END_MAP_TEST_INPUT, END_MAP_TEST_INPUT.length()); + assertEquals("End map not parsed correctly", "other", token); + + token = CommandLineParser.getCurrentToken(START_LIST_TEST_INPUT, START_LIST_TEST_INPUT.length()); + assertNull("Start list not parsed correctly", token); + + token = CommandLineParser.getCurrentToken(LIST_TEST_INPUT, LIST_TEST_INPUT.length()); + assertEquals("List not parsed correctly", "other", token); + + token = CommandLineParser.getCurrentToken(COMMAND_ARGUMENTS_TEST_INPUT, COMMAND_ARGUMENTS_TEST_INPUT.length()); + assertEquals("Command with arguments not parsed correctly", "argum", token); + + token = CommandLineParser.getCurrentToken(COMMAND_NAME_TEST_INPUT, COMMAND_NAME_TEST_INPUT.length()); + assertEquals("Command name not parsed correctly", "com", token); + + token = CommandLineParser.getCurrentToken(COMMENT_TEST_INPUT, COMMENT_TEST_INPUT.length()); + assertNull("Comment not parsed correctly", token); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandNamesCompleterTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandNamesCompleterTests.java new file mode 100755 index 000000000..390ffa743 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CommandNamesCompleterTests.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.junit.Assert.*; +import static org.easymock.EasyMock.*; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.junit.Test; + +public class CommandNamesCompleterTests { + + private static final String COMMANDS = ".commands"; + + @Test + public void testGetCandidates() { + Set<String> commands = new HashSet<String>(); + commands.add("equinox:bundles"); + commands.add("equinox:diag"); + commands.add("equinox:setprop"); + commands.add("gogo:lb"); + commands.add("gogo:echo"); + commands.add("gogo:set"); + + CommandSession session = createMock(CommandSession.class); + expect(session.get(COMMANDS)).andReturn(commands).times(4); + replay(session); + + CommandNamesCompleter completer = new CommandNamesCompleter(session); + Map<String, Integer> candidates; + + candidates = completer.getCandidates("se", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("set should be in the resultset, but it is not", candidates.get("set")); + assertNotNull("setprop should be in the resultset, but it is not", candidates.get("setprop")); + + candidates = completer.getCandidates("equinox:bun", "equinox:bun".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("equinox:bundles should be in the resultset, but it is not", candidates.get("equinox:bundles")); + + candidates = completer.getCandidates("ec", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("echo should be in the resultset, but it is not", candidates.get("echo")); + + candidates = completer.getCandidates("head", 4); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + verify(session); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CompletionHandlerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CompletionHandlerTests.java new file mode 100755 index 000000000..e71be0c12 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/CompletionHandlerTests.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.junit.Assert.*; +import static org.easymock.EasyMock.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.completion.common.Completer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +public class CompletionHandlerTests { + + private static final String COMMANDS = ".commands"; + private static final String WORK_DIR_NAME = "work"; + private static final String TESTFILE = "testfile"; + private static final String TESTOUTPUT = "testoutput"; + private static final String FILE = "file"; + + @Before + public void init() throws IOException { + File currentDir = new File("."); + File[] files = currentDir.listFiles(); + for (File file : files) { + if(file.getName().equals(WORK_DIR_NAME)) { + clean(); + break; + } + } + + File workDir = new File(currentDir.getAbsolutePath() + File.separator + WORK_DIR_NAME); + workDir.mkdir(); + + createFile(workDir, TESTFILE); + createFile(workDir, TESTOUTPUT); + createFile(workDir, FILE); + } + + @Test + public void testGetCandidates() throws Exception { + BundleContext context = createMock(BundleContext.class); + expect(context.getServiceReferences(Completer.class.getName(), null)).andReturn(null).anyTimes(); + replay(context); + + Set<String> variables = new HashSet<String>(); + variables.add("SCOPE"); + variables.add("PROMPT"); + variables.add("ECHO_ON"); + variables.add("ECHO"); + + Set<String> commands = new HashSet<String>(); + commands.add("equinox:bundles"); + commands.add("equinox:diag"); + commands.add("equinox:setprop"); + commands.add("gogo:lb"); + commands.add("gogo:echo"); + commands.add("gogo:set"); + + CommandSession session = createMock(CommandSession.class); + expect(session.get(null)).andReturn(variables).anyTimes(); + expect(session.get(COMMANDS)).andReturn(commands).anyTimes(); + replay(session); + + CompletionHandler completer = new CompletionHandler(context, session); + Map<String, Integer> candidates; + + candidates = completer.getCandidates("$SC".getBytes(), 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("SCOPE should be in the resultset, but it is not", candidates.get("SCOPE")); + + candidates = completer.getCandidates("$EC".getBytes(), 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("ECHO_ON should be in the resultset, but it is not", candidates.get("ECHO_ON")); + assertNotNull("ECHO should be in the resultset, but it is not", candidates.get("ECHO")); + + candidates = completer.getCandidates("$AB".getBytes(), 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + completer = new CompletionHandler(context, session); + candidates = completer.getCandidates("se".getBytes(), 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("set should be in the resultset, but it is not", candidates.get("set")); + assertNotNull("setprop should be in the resultset, but it is not", candidates.get("setprop")); + + candidates = completer.getCandidates("equinox:bun".getBytes(), "equinox:bun".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("equinox:bundles should be in the resultset, but it is not", candidates.get("equinox:bundles")); + + candidates = completer.getCandidates("ec".getBytes(), 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("echo should be in the resultset, but it is not", candidates.get("echo")); + + candidates = completer.getCandidates("head".getBytes(), "head".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + completer = new CompletionHandler(context, session); + candidates = completer.getCandidates("wor".getBytes(), "wor".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("work should be in the resultset, but it is not", candidates.get(WORK_DIR_NAME)); + + candidates = completer.getCandidates("work/test".getBytes(), "work/test".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("testfile should be in the resultset, but it is not", candidates.get(TESTFILE)); + assertNotNull("testoutput should be in the resultset, but it is not", candidates.get(TESTOUTPUT)); + + candidates = completer.getCandidates("work/".getBytes(), "work/".length()); + assertEquals("Candidates not as expected", 3, candidates.size()); + assertNotNull("testfile should be in the resultset, but it is not", candidates.get(TESTFILE)); + assertNotNull("testoutput should be in the resultset, but it is not", candidates.get(TESTOUTPUT)); + assertNotNull("file should be in the resultset, but it is not", candidates.get(FILE)); + } + + @After + public void cleanUp() { + clean(); + } + + private void clean() { + File currentFile = new File("."); + File workDir = new File(currentFile.getAbsolutePath() + File.separator + WORK_DIR_NAME); + File[] files = workDir.listFiles(); + for (File file : files) { + file.delete(); + } + workDir.delete(); + } + + private void createFile(File parentDir, String filename) throws IOException { + File file = new File(parentDir.getAbsolutePath() + File.separator + filename); + PrintWriter out = new PrintWriter(new FileOutputStream(file)); + out.write(filename); + out.flush(); + out.close(); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/FileNamesCompleterTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/FileNamesCompleterTests.java new file mode 100755 index 000000000..91540a13f --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/FileNamesCompleterTests.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class FileNamesCompleterTests { + private static final String WORK_DIR_NAME = "work"; + private static final String TESTFILE = "testfile"; + private static final String TESTOUTPUT = "testoutput"; + private static final String FILE = "file"; + + @Before + public void init() throws IOException { + File currentDir = new File("."); + File[] files = currentDir.listFiles(); + for (File file : files) { + if(file.getName().equals(WORK_DIR_NAME)) { + clean(); + break; + } + } + + File workDir = new File(currentDir.getAbsolutePath() + File.separator + WORK_DIR_NAME); + workDir.mkdir(); + + createFile(workDir, TESTFILE); + createFile(workDir, TESTOUTPUT); + createFile(workDir, FILE); + } + + @Test + public void testGetCandidates() { + FileNamesCompleter completer = new FileNamesCompleter(); + + Map<String, Integer> candidates; + + candidates = completer.getCandidates("wor", "wor".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("work should be in the resultset, but it is not", candidates.get(WORK_DIR_NAME)); + + candidates = completer.getCandidates("work/test", "work/test".length()); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("testfile should be in the resultset, but it is not", candidates.get(TESTFILE)); + assertNotNull("testoutput should be in the resultset, but it is not", candidates.get(TESTOUTPUT)); + + candidates = completer.getCandidates(WORK_DIR_NAME + "/", 5); + assertEquals("Candidates not as expected", 3, candidates.size()); + assertNotNull("testfile should be in the resultset, but it is not", candidates.get(TESTFILE)); + assertNotNull("testoutput should be in the resultset, but it is not", candidates.get(TESTOUTPUT)); + assertNotNull("file should be in the resultset, but it is not", candidates.get(FILE)); + } + + @After + public void cleanUp() { + clean(); + } + + private void clean() { + File currentFile = new File("."); + File workDir = new File(currentFile.getAbsolutePath() + File.separator + WORK_DIR_NAME); + File[] files = workDir.listFiles(); + for (File file : files) { + file.delete(); + } + workDir.delete(); + } + + private void createFile(File parentDir, String filename) throws IOException { + File file = new File(parentDir.getAbsolutePath() + File.separator + filename); + PrintWriter out = new PrintWriter(new FileOutputStream(file)); + out.write(filename); + out.flush(); + out.close(); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/StringsCompleterTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/StringsCompleterTests.java new file mode 100755 index 000000000..05096ba5b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/StringsCompleterTests.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; + +public class StringsCompleterTests { + + @Test + public void testGetCandidates() { + Set<String> strings = new HashSet<String>(); + strings.add("command"); + strings.add("SCOPE"); + strings.add("equinox:bundles"); + strings.add("common"); + + StringsCompleter completer = new StringsCompleter(strings, false); + Map<String, Integer> candidates; + + candidates = completer.getCandidates("sco", 3); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("SCOPE should be in the resultset, but it is not", candidates.get("SCOPE")); + + candidates = completer.getCandidates("com", 3); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("command should be in the resultset, but it is not", candidates.get("command")); + assertNotNull("common should be in the resultset, but it is not", candidates.get("common")); + + candidates = completer.getCandidates("tr", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + completer = new StringsCompleter(strings, true); + + candidates = completer.getCandidates("sco", 3); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + candidates = completer.getCandidates("SCO", 3); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("SCOPE should be in the resultset, but it is not", candidates.get("SCOPE")); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/VariableNamesCompleterTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/VariableNamesCompleterTests.java new file mode 100755 index 000000000..2b6ca5290 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/completion/VariableNamesCompleterTests.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.junit.Test; + +public class VariableNamesCompleterTests { + + @Test + public void testGetCandidates() { + Set<String> variables = new HashSet<String>(); + variables.add("SCOPE"); + variables.add("PROMPT"); + variables.add("ECHO_ON"); + variables.add("ECHO"); + + CommandSession session = createMock(CommandSession.class); + expect(session.get(null)).andReturn(variables).times(3); + replay(session); + + VariableNamesCompleter completer = new VariableNamesCompleter(session); + Map<String, Integer> candidates; + + candidates = completer.getCandidates("SC", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 1, candidates.size()); + assertNotNull("SCOPE should be in the resultset, but it is not", candidates.get("SCOPE")); + + candidates = completer.getCandidates("EC", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 2, candidates.size()); + assertNotNull("ECHO_ON should be in the resultset, but it is not", candidates.get("ECHO_ON")); + assertNotNull("ECHO should be in the resultset, but it is not", candidates.get("ECHO")); + + candidates = completer.getCandidates("AB", 2); + assertNotNull("Candidates null", candidates); + assertEquals("Candidates not as expected", 0, candidates.size()); + + verify(session); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/RolePrincipalTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/RolePrincipalTests.java new file mode 100755 index 000000000..edae26f3b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/RolePrincipalTests.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.jaas; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RolePrincipalTests { + + private static final String ROLE_NAME = "administrator"; + + @Test + public void testHashCode() { + RolePrincipal role = new RolePrincipal(ROLE_NAME); + assertEquals("Role hash code not as expected", 73 + ROLE_NAME.hashCode(), role.hashCode()); + } + + @Test + public void testGetName() { + RolePrincipal role = new RolePrincipal(ROLE_NAME); + assertEquals("Role not as expected", ROLE_NAME, role.getName()); + } + + @Test + public void testEqualsObject() { + RolePrincipal role = new RolePrincipal(ROLE_NAME); + RolePrincipal sameRole = new RolePrincipal(ROLE_NAME); + RolePrincipal emptyRole = new RolePrincipal(null); + + assertTrue("Roles should be equal", role.equals(role)); + assertTrue("Roles should be equal", role.equals(sameRole)); + assertFalse("Roles should not be equal", role.equals(emptyRole)); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/UserPrincipalTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/UserPrincipalTests.java new file mode 100755 index 000000000..50239be1c --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/jaas/UserPrincipalTests.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.jaas; + +import static org.junit.Assert.*; + +import java.util.Set; + +import org.junit.BeforeClass; +import org.junit.Test; + +public class UserPrincipalTests { + + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String OTHERUSER = "otheruser"; + private static final String OTHERPASSWORD = "otherpass"; + private static final String WRONG_PASS = "wrong_pass"; + private static final String ROLE = "administrator"; + private static final String OTHERROLE = "otherrole"; + + @Test + public void testHashCode() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + RolePrincipal role = new RolePrincipal(ROLE); + user.addRole(role); + + int expectedHash = 73 + USERNAME.hashCode(); + expectedHash = 73*expectedHash + PASSWORD.hashCode(); + expectedHash = 73*expectedHash + role.hashCode(); + assertEquals("User hash code not as expected", expectedHash, user.hashCode()); + } + + @Test + public void testGetName() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + assertEquals("Username not as expected", USERNAME, user.getName()); + } + + @Test + public void testAuthenticate() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + assertTrue("User should be successfully authenticated", user.authenticate(PASSWORD.toCharArray())); + assertFalse("User should not be authenticated", user.authenticate(WRONG_PASS.toCharArray())); + } + + @Test + public void testGetRoles() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + RolePrincipal role = new RolePrincipal(ROLE); + user.addRole(role); + Set<RolePrincipal> roles = user.getRoles(); + assertEquals("There should be one role", 1, roles.size()); + assertTrue("User roles should contain the role administrator", roles.contains(role)); + } + + @Test + public void testEqualsObject() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + RolePrincipal role = new RolePrincipal(ROLE); + user.addRole(role); + + UserPrincipal sameUser = new UserPrincipal(USERNAME, PASSWORD); + RolePrincipal sameRole = new RolePrincipal(ROLE); + sameUser.addRole(sameRole); + + UserPrincipal otherUser = new UserPrincipal(OTHERUSER, OTHERPASSWORD); + RolePrincipal otherRole = new RolePrincipal(OTHERROLE); + otherUser.addRole(otherRole); + + UserPrincipal userOtherRole = new UserPrincipal(USERNAME, PASSWORD); + RolePrincipal otherRolePrincipal = new RolePrincipal(OTHERROLE); + userOtherRole.addRole(otherRolePrincipal); + + assertTrue("User should be equal to itself", user.equals(user)); + assertTrue("Users should be equal", user.equals(sameUser)); + assertFalse("Users should not be equal", user.equals(otherUser)); + assertFalse("Users should not be equal", user.equals(userOtherRole)); + } + + @Test + public void testDestroy() { + UserPrincipal user = new UserPrincipal(USERNAME, PASSWORD); + UserPrincipal same = new UserPrincipal(USERNAME, PASSWORD); + + user.destroy(); + assertFalse("Users should not be equal", user.equals(same)); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandTests.java new file mode 100755 index 000000000..7fab9788b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandTests.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringBufferInputStream; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.sshd.ClientChannel; +import org.apache.sshd.ClientSession; +import org.apache.sshd.SshClient; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.future.DefaultConnectFuture; +import org.apache.sshd.server.Environment; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleContext; + + +public class SshCommandTests { + private static final int TEST_CONTENT = 100; + private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file"; + private static final String JAAS_CONFIG_FILE_NAME = "jaas.config"; + private static final String JAAS_CONFIG_PROPERTY_NAME = "java.security.auth.login.config"; + private static final String DEFAULT_USER_STORAGE = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String SSH_PORT_PROP_NAME = "osgi.console.ssh"; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String STORE_FILE_NAME = SshCommandTests.class.getName() + "_store"; + private static final String GOGO_SHELL_COMMAND = "gosh --login --noshutdown"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String START_COMMAND = "start"; + private static final String STOP_COMMAND = "stop"; + private static final String TERM_PROPERTY = "TERM"; + private static final String XTERM = "XTERM"; + private static final String HOST = "localhost"; + private static final int SSH_PORT = 2222; + private static final long WAIT_TIME = 5000; + + @Before + public void init() throws Exception { + clean(); + initStore(); + initJaasConfigFile(); + } + + @Test + public void testSshCommand() throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + EasyMock.makeThreadSafe(session, true); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(5); + EasyMock.expect(session.execute(GOGO_SHELL_COMMAND)).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.makeThreadSafe(context, true); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(FALSE); + EasyMock.expect(context.getProperty(DEFAULT_USER_STORAGE)).andReturn(TRUE).anyTimes(); + EasyMock.expect(context.getProperty(SSH_PORT_PROP_NAME)).andReturn(Integer.toString(SSH_PORT)); + EasyMock.expect(context.registerService((String)EasyMock.anyObject(), EasyMock.anyObject(), (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + Map<String, String> environment = new HashMap<String, String>(); + environment.put(TERM_PROPERTY, XTERM); + Environment env = EasyMock.createMock(Environment.class); + EasyMock.expect(env.getEnv()).andReturn(environment); + EasyMock.replay(env); + + SshCommand command = new SshCommand(processor, context); + command.ssh(new String[] {START_COMMAND}); + + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + try { + ConnectFuture connectFuture = client.connect(HOST, SSH_PORT); + DefaultConnectFuture defaultConnectFuture = (DefaultConnectFuture) connectFuture; + + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + ClientSession sshSession = defaultConnectFuture.getSession(); + + int ret = ClientSession.WAIT_AUTH; + sshSession.authPassword(USERNAME, PASSWORD); + ret = sshSession.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0); + + if ((ret & ClientSession.CLOSED) != 0) { + System.err.println("error"); + System.exit(-1); + } + ClientChannel channel = sshSession.createChannel("shell"); + channel.setIn(new StringBufferInputStream(TEST_CONTENT + "\n")); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + channel.setOut(byteOut); + channel.setErr(byteOut); + channel.open(); + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + byte[] output = byteOut.toByteArray(); + Assert.assertEquals("Output not as expected",Integer.toString(TEST_CONTENT), new String(output).trim()); + sshSession.close(true); + } finally { + client.stop(); + } + + command.ssh(new String[] {STOP_COMMAND}); + return; + } + + @After + public void cleanUp() { + clean(); + } + + private void clean() { + System.setProperty(USER_STORE_FILE_NAME, ""); + File file = new File(STORE_FILE_NAME); + if (file.exists()) { + file.delete(); + } + + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, ""); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (jaasConfFile.exists()) { + jaasConfFile.delete(); + } + } + + private void initStore() throws Exception { + System.setProperty(USER_STORE_FILE_NAME, STORE_FILE_NAME); + SecureUserStore.initStorage(); + SecureUserStore.putUser(USERNAME, DigestUtil.encrypt(PASSWORD), null); + } + + private void initJaasConfigFile() throws Exception { + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, JAAS_CONFIG_FILE_NAME); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (!jaasConfFile.exists()) { + PrintWriter out = null; + try { + out = new PrintWriter(jaasConfFile); + out.println("equinox_console {"); + out.println(" org.eclipse.equinox.console.jaas.SecureStorageLoginModule REQUIRED;"); + out.println("};"); + } finally { + if (out != null) { + out.close(); + } + } + } + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandWithConfigAdminTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandWithConfigAdminTests.java new file mode 100755 index 000000000..099442d77 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshCommandWithConfigAdminTests.java @@ -0,0 +1,310 @@ +package org.eclipse.equinox.console.ssh; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringBufferInputStream; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.sshd.ClientChannel; +import org.apache.sshd.ClientSession; +import org.apache.sshd.SshClient; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.future.DefaultConnectFuture; +import org.apache.sshd.common.RuntimeSshException; +import org.apache.sshd.server.Environment; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.BundleListener; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkListener; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ManagedService; + + +public class SshCommandWithConfigAdminTests { + private static final int TEST_CONTENT = 100; + private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file"; + private static final String JAAS_CONFIG_FILE_NAME = "jaas.config"; + private static final String JAAS_CONFIG_PROPERTY_NAME = "java.security.auth.login.config"; + private static final String DEFAULT_USER_STORAGE = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String STORE_FILE_NAME = SshCommandTests.class.getName() + "_store"; + private static final String GOGO_SHELL_COMMAND = "gosh --login --noshutdown"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String STOP_COMMAND = "stop"; + private static final String TERM_PROPERTY = "TERM"; + private static final String XTERM = "XTERM"; + private static final String HOST = "localhost"; + private static final String SSH_PORT = "2222"; + private static final long WAIT_TIME = 5000; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private ManagedService configurator; + + @Before + public void init() throws Exception { + clean(); + initStore(); + initJaasConfigFile(); + } + + @Test + public void testSshCommandWithConfigAdmin() throws Exception { + + CommandSession session = EasyMock.createMock(CommandSession.class); + EasyMock.makeThreadSafe(session, true); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(5); + EasyMock.expect(session.execute(GOGO_SHELL_COMMAND)).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + final ServiceRegistration<?> registration = EasyMock.createMock(ServiceRegistration.class); + registration.setProperties((Dictionary)EasyMock.anyObject()); + EasyMock.expectLastCall(); + EasyMock.replay(registration); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.makeThreadSafe(context, true); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(TRUE); + EasyMock.expect(context.getProperty(DEFAULT_USER_STORAGE)).andReturn(TRUE).anyTimes(); + EasyMock.expect( + (ServiceRegistration) context.registerService( + (String)EasyMock.anyObject(), + (ManagedService)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject()) + ).andAnswer((IAnswer<ServiceRegistration<?>>) new IAnswer<ServiceRegistration<?>>() { + public ServiceRegistration<?> answer() { + configurator = (ManagedService) EasyMock.getCurrentArguments()[1]; + return registration; + } + }); + EasyMock.expect( + context.registerService( + (String)EasyMock.anyObject(), + (SshCommand)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + Map<String, String> environment = new HashMap<String, String>(); + environment.put(TERM_PROPERTY, XTERM); + Environment env = EasyMock.createMock(Environment.class); + EasyMock.expect(env.getEnv()).andReturn(environment); + EasyMock.replay(env); + + SshCommand command = new SshCommand(processor, context); + Dictionary props = new Hashtable(); + props.put("port", SSH_PORT); + props.put("host", HOST); + props.put("enabled", TRUE); + configurator.updated(props); + + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + try { + ConnectFuture connectFuture = client.connect(HOST, Integer.valueOf(SSH_PORT)); + DefaultConnectFuture defaultConnectFuture = (DefaultConnectFuture) connectFuture; + + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + ClientSession sshSession = defaultConnectFuture.getSession(); + + int ret = ClientSession.WAIT_AUTH; + sshSession.authPassword(USERNAME, PASSWORD); + ret = sshSession.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0); + + if ((ret & ClientSession.CLOSED) != 0) { + System.err.println("error"); + System.exit(-1); + } + ClientChannel channel = sshSession.createChannel("shell"); + channel.setIn(new StringBufferInputStream(TEST_CONTENT + "\n")); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + channel.setOut(byteOut); + channel.setErr(byteOut); + channel.open(); + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + byte[] output = byteOut.toByteArray(); + Assert.assertEquals("Output not as expected",Integer.toString(TEST_CONTENT), new String(output).trim()); + sshSession.close(true); + } finally { + client.stop(); + } + + command.ssh(new String[] {STOP_COMMAND}); + return; + } + + @Test + public void testSshCommandWithConfigAdminDisabledSsh() throws Exception { + testDisabled(false); + } + + @Test + public void testSshCommandWithConfigAdminDisabledSshByDefault() throws Exception { + testDisabled(true); + } + + private void testDisabled(boolean isDefault) throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(4); + EasyMock.expect(session.execute(GOGO_SHELL_COMMAND)).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + final ServiceRegistration<?> registration = EasyMock.createMock(ServiceRegistration.class); + registration.setProperties((Dictionary)EasyMock.anyObject()); + EasyMock.expectLastCall(); + EasyMock.replay(registration); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(TRUE); + EasyMock.expect(context.getProperty(DEFAULT_USER_STORAGE)).andReturn(TRUE).anyTimes(); + EasyMock.expect( + (ServiceRegistration) context.registerService( + (String)EasyMock.anyObject(), + (ManagedService)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject()) + ).andAnswer((IAnswer<ServiceRegistration<?>>) new IAnswer<ServiceRegistration<?>>() { + public ServiceRegistration<?> answer() { + configurator = (ManagedService) EasyMock.getCurrentArguments()[1]; + return registration; + } + }); + EasyMock.expect( + context.registerService( + (String)EasyMock.anyObject(), + (SshCommand)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + Map<String, String> environment = new HashMap<String, String>(); + environment.put(TERM_PROPERTY, XTERM); + Environment env = EasyMock.createMock(Environment.class); + EasyMock.expect(env.getEnv()).andReturn(environment); + EasyMock.replay(env); + + SshCommand command = new SshCommand(processor, context); + Dictionary props = new Hashtable(); + props.put("port", SSH_PORT); + props.put("host", HOST); + if (isDefault == false) { + props.put("enabled", FALSE); + } + configurator.updated(props); + + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + try { + ConnectFuture connectFuture = client.connect(HOST, Integer.valueOf(SSH_PORT)); + DefaultConnectFuture defaultConnectFuture = (DefaultConnectFuture) connectFuture; + + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + ClientSession sshSession; + try { + sshSession = defaultConnectFuture.getSession(); + Assert.fail("It should not be possible to connect to " + HOST + ":" + SSH_PORT); + } catch (RuntimeSshException e) { + //this is expected + } + } finally { + client.stop(); + } + + try { + command.ssh(new String[] {STOP_COMMAND}); + } catch (IllegalStateException e) { + // this is expected + } + return; + } + + @After + public void cleanUp() { + clean(); + } + + private void clean() { + System.setProperty(USER_STORE_FILE_NAME, ""); + File file = new File(STORE_FILE_NAME); + if (file.exists()) { + file.delete(); + } + + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, ""); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (jaasConfFile.exists()) { + jaasConfFile.delete(); + } + } + + private void initStore() throws Exception { + System.setProperty(USER_STORE_FILE_NAME, STORE_FILE_NAME); + SecureUserStore.initStorage(); + SecureUserStore.putUser(USERNAME, DigestUtil.encrypt(PASSWORD), null); + } + + private void initJaasConfigFile() throws Exception { + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, JAAS_CONFIG_FILE_NAME); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (!jaasConfFile.exists()) { + PrintWriter out = null; + try { + out = new PrintWriter(jaasConfFile); + out.println("equinox_console {"); + out.println(" org.eclipse.equinox.console.jaas.SecureStorageLoginModule REQUIRED;"); + out.println("};"); + } finally { + if (out != null) { + out.close(); + } + } + } + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshDisconnectCommandTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshDisconnectCommandTests.java new file mode 100755 index 000000000..8ab10b244 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshDisconnectCommandTests.java @@ -0,0 +1,234 @@ +package org.eclipse.equinox.console.ssh; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringBufferInputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.sshd.ClientChannel; +import org.apache.sshd.ClientSession; +import org.apache.sshd.SshClient; +import org.apache.sshd.client.future.ConnectFuture; +import org.apache.sshd.client.future.DefaultConnectFuture; +import org.apache.sshd.server.Environment; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.eclipse.equinox.console.commands.DisconnectCommand; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +public class SshDisconnectCommandTests { + private static final int TEST_CONTENT = 100; + private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file"; + private static final String JAAS_CONFIG_FILE_NAME = "jaas.config"; + private static final String JAAS_CONFIG_PROPERTY_NAME = "java.security.auth.login.config"; + private static final String DEFAULT_USER_STORAGE = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String SSH_PORT_PROP_NAME = "osgi.console.ssh"; + private static final String STORE_FILE_NAME = SshCommandTests.class.getName() + "_store"; + private static final String GOGO_SHELL_COMMAND = "gosh --login --noshutdown"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String START_COMMAND = "start"; + private static final String STOP_COMMAND = "stop"; + private static final String TERM_PROPERTY = "TERM"; + private static final String XTERM = "XTERM"; + private static final String HOST = "localhost"; + private static final int SSH_PORT = 2222; + private static final long WAIT_TIME = 5000; + private SshSession sshSession; + private InputStream in; + + @Before + public void init() throws Exception { + clean(); + initStore(); + initJaasConfigFile(); + } + + @Test + public void testSshCommand() throws Exception { + final CommandSession session = EasyMock.createMock(CommandSession.class); + EasyMock.makeThreadSafe(session, true); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall(); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall(); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall(); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall(); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() { + + @Override + public Object answer() throws Throwable { + sshSession = (SshSession)EasyMock.getCurrentArguments()[1]; + return null; + } + + }); + EasyMock.expect(session.execute(GOGO_SHELL_COMMAND)).andReturn(null); + EasyMock.expect(session.get("CLOSEABLE")).andReturn(sshSession); + session.close(); + EasyMock.expectLastCall().atLeastOnce(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.makeThreadSafe(context, true); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(FALSE); + EasyMock.expect(context.getProperty(DEFAULT_USER_STORAGE)).andReturn(TRUE).anyTimes(); + EasyMock.expect(context.getProperty(SSH_PORT_PROP_NAME)).andReturn(Integer.toString(SSH_PORT)); + EasyMock.expect(context.registerService((String)EasyMock.anyObject(), EasyMock.anyObject(), (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + Map<String, String> environment = new HashMap<String, String>(); + environment.put(TERM_PROPERTY, XTERM); + Environment env = EasyMock.createMock(Environment.class); + EasyMock.expect(env.getEnv()).andReturn(environment); + EasyMock.replay(env); + + SshCommand command = new SshCommand(processor, context); + command.ssh(new String[] {START_COMMAND}); + + SshClient client = SshClient.setUpDefaultClient(); + client.start(); + try { + ConnectFuture connectFuture = client.connect(HOST, SSH_PORT); + DefaultConnectFuture defaultConnectFuture = (DefaultConnectFuture) connectFuture; + + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + ClientSession sshSession = defaultConnectFuture.getSession(); + + int ret = ClientSession.WAIT_AUTH; + sshSession.authPassword(USERNAME, PASSWORD); + ret = sshSession.waitFor(ClientSession.WAIT_AUTH | ClientSession.CLOSED | ClientSession.AUTHED, 0); + + if ((ret & ClientSession.CLOSED) != 0) { + System.err.println("error"); + System.exit(-1); + } + ClientChannel channel = sshSession.createChannel("shell"); + + PipedOutputStream outputStream = new PipedOutputStream(); + PipedInputStream inputStream = new PipedInputStream(outputStream); + + final DisconnectCommand disconnectCommand = new DisconnectCommand(context); + in = System.in; + System.setIn(inputStream); + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + channel.setIn(new StringBufferInputStream(TEST_CONTENT + "\n")); + channel.setOut(byteOut); + channel.setErr(byteOut); + channel.open(); + + + new Thread() { + public void run() { + disconnectCommand.disconnect(session); + } + }.start(); + + outputStream.write(new byte[]{'y'}); + outputStream.write('\n'); + outputStream.flush(); + + Thread.sleep(WAIT_TIME); + + try { + Thread.sleep(WAIT_TIME); + } catch (InterruptedException ie) { + // do nothing + } + + try { + outputStream.write(TEST_CONTENT); + outputStream.write('\n'); + outputStream.flush(); + Assert.fail("Connection not closed"); + } catch (Exception e) { + // we should be here + } + sshSession.close(true); + } finally { + client.stop(); + } + + command.ssh(new String[] {STOP_COMMAND}); + return; + } + + @After + public void cleanUp() { + clean(); + } + + private void clean() { + System.setProperty(USER_STORE_FILE_NAME, ""); + File file = new File(STORE_FILE_NAME); + if (file.exists()) { + file.delete(); + } + + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, ""); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (jaasConfFile.exists()) { + jaasConfFile.delete(); + } + + System.setIn(in); + } + + private void initStore() throws Exception { + System.setProperty(USER_STORE_FILE_NAME, STORE_FILE_NAME); + SecureUserStore.initStorage(); + SecureUserStore.putUser(USERNAME, DigestUtil.encrypt(PASSWORD), null); + } + + private void initJaasConfigFile() throws Exception { + System.setProperty(JAAS_CONFIG_PROPERTY_NAME, JAAS_CONFIG_FILE_NAME); + File jaasConfFile = new File(JAAS_CONFIG_FILE_NAME); + if (!jaasConfFile.exists()) { + PrintWriter out = null; + try { + out = new PrintWriter(jaasConfFile); + out.println("equinox_console {"); + out.println(" org.eclipse.equinox.console.jaas.SecureStorageLoginModule REQUIRED;"); + out.println("};"); + } finally { + if (out != null) { + out.close(); + } + } + } + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputHandlerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputHandlerTests.java new file mode 100755 index 000000000..e7ed407a0 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputHandlerTests.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.ByteArrayOutputStream; +import java.io.StringBufferInputStream; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.ssh.SshInputHandler; +import org.junit.Assert; +import org.junit.Test; + + +public class SshInputHandlerTests { + + 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); + SshInputHandler handler = new SshInputHandler(input, in, out); + 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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputScannerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputScannerTests.java new file mode 100755 index 000000000..5f246fbdc --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshInputScannerTests.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.junit.Assert; +import org.junit.Test; + + +public class SshInputScannerTests { + + private static final byte ESC = 27; + + @Test + public void testScan() throws Exception { + ConsoleInputStream in = new ConsoleInputStream(); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + ConsoleOutputStream out = new ConsoleOutputStream(byteOut); + SshInputScanner scanner = new SshInputScanner(in, out); + try { + 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); + SshInputScanner scanner = new SshInputScanner(in, out); + + 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 ab, but read " + output, output.equals("ab")); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshShellTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshShellTests.java new file mode 100755 index 000000000..9c041fdf0 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/SshShellTests.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.apache.sshd.server.Environment; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.BundleContext; + + +public class SshShellTests { + + private static final int TEST_CONTENT = 100; + private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file"; + private static final String DEFAULT_USER_STORAGE = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String USER_STORE_NAME = SshShellTests.class.getName() + "_store"; + private static final String HOST = "localhost"; + private static final String GOGO_SHELL_COMMAND = "gosh --login --noshutdown"; + private static final String TERM_PROPERTY = "TERM"; + private static final String XTERM = "XTERM"; + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private static final String TRUE = "true"; + + @Before + public void init() throws Exception { + clean(); + initStore(); + } + + @Test + public void testSshConnection() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + SshShell shell = null; + OutputStream outClient = null; + OutputStream outServer = null; + + try { + + servSocket = new ServerSocket(0); + socketClient = new Socket(HOST, servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + CommandSession session = EasyMock.createMock(CommandSession.class); + EasyMock.makeThreadSafe(session, true); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(5); + EasyMock.expect(session.execute(GOGO_SHELL_COMMAND)).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.makeThreadSafe(context, true); + EasyMock.expect(context.getProperty(DEFAULT_USER_STORAGE)).andReturn(TRUE); + EasyMock.replay(context); + + Map<String, String> environment = new HashMap<String, String>(); + environment.put(TERM_PROPERTY, XTERM); + Environment env = EasyMock.createMock(Environment.class); + EasyMock.expect(env.getEnv()).andReturn(environment); + EasyMock.replay(env); + + List<CommandProcessor> processors = new ArrayList<CommandProcessor>(); + processors.add(processor); + shell = new SshShell(processors, context); + shell.setInputStream(socketServer.getInputStream()); + shell.setOutputStream(socketServer.getOutputStream()); + shell.start(env); + + outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + InputStream input = socketClient.getInputStream(); + int in = input.read(); + Assert.assertTrue("Server received [" + in + "] instead of " + TEST_CONTENT + " from the ssh client.", in == TEST_CONTENT); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (outClient != null) { + outClient.close(); + } + if (outServer != null) { + outServer.close(); + } + + if (socketServer != null) { + socketServer.close(); + } + + if (servSocket != null) { + servSocket.close(); + } + + } + } + + @After + public void cleanUp() { + clean(); + } + + private void initStore() throws Exception { + System.setProperty(USER_STORE_FILE_NAME, USER_STORE_NAME); + SecureUserStore.initStorage(); + SecureUserStore.putUser(USERNAME, DigestUtil.encrypt(PASSWORD), null); + } + + private void clean() { + System.setProperty(USER_STORE_FILE_NAME, ""); + File file = new File(USER_STORE_NAME); + if(file.exists()) { + file.delete(); + } + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/UserAdminCommandTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/UserAdminCommandTests.java new file mode 100755 index 000000000..658690178 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/ssh/UserAdminCommandTests.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.junit.After; +import org.junit.Test; + + +public class UserAdminCommandTests { + + private static final String USER_STORE_FILE_NAME_PROPERTY = "org.eclipse.equinox.console.jaas.file"; + private static final String USER_STORE_FILE_NAME = UserAdminCommandTests.class.getName() + "_store"; + private static final String USERNAME_OPTION = "-username"; + private static final String PASSWORD_OPTION = "-password"; + private static final String ROLES_OPTION = "-roles"; + private static final String USERNAME1 = "username1"; + private static final String USERNAME2 = "username2"; + private static final String PASSWORD1 = "password1"; + private static final String PASSWORD2 = "password2"; + private static final String ROLES1 = "role1,role2"; + private static final String ROLES2 = "role3,role4"; + private static final String ROLES_TO_REMOVE = "role2"; + private static final String REMAINING_ROLES = "role1"; + + @Test + public void testCommand() throws Exception { + cleanUp(); + + System.setProperty(USER_STORE_FILE_NAME_PROPERTY, USER_STORE_FILE_NAME); + SecureUserStore.initStorage(); + + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.replay(session); + + UserAdminCommand command = new UserAdminCommand(); + command.addUser(new String[] {USERNAME_OPTION, USERNAME1, PASSWORD_OPTION, PASSWORD1}); + command.addUser(new String[] {USERNAME_OPTION, USERNAME2, PASSWORD_OPTION, PASSWORD2, ROLES_OPTION, ROLES2}); + + String[] usernames = SecureUserStore.getUserNames(); + boolean arePresent = (usernames[0].equals(USERNAME1) || usernames[0].equals(USERNAME2)) && (usernames[1].equals(USERNAME1) || usernames[1].equals(USERNAME2)) && (!usernames[0].equals(usernames[1])); + assertTrue("Usernames not correctly saved", arePresent); + + String pass1 = SecureUserStore.getPassword(USERNAME1); + String pass2 = SecureUserStore.getPassword(USERNAME2); + assertTrue("Passwords not correctly saved", pass1.equals(DigestUtil.encrypt(PASSWORD1)) && pass2.equals(DigestUtil.encrypt(PASSWORD2))); + + String roles = SecureUserStore.getRoles(USERNAME2); + assertEquals("Roles for the second user are not as expected", ROLES2, roles); + + command.addRoles(new String[] {USERNAME_OPTION, USERNAME1, ROLES_OPTION, ROLES1}); + roles = SecureUserStore.getRoles(USERNAME1); + boolean areRolesEqual = compareRoles(ROLES1, roles); + assertTrue("Roles for the first user are not as expected", areRolesEqual); + + command.removeRoles(new String[] {USERNAME_OPTION, USERNAME1, ROLES_OPTION, ROLES_TO_REMOVE}); + roles = SecureUserStore.getRoles(USERNAME1); + areRolesEqual = compareRoles(REMAINING_ROLES, roles); + assertTrue("Roles for the first user are not as expected", areRolesEqual); + + command.resetPassword(USERNAME1); + String pass = SecureUserStore.getPassword(USERNAME1); + assertNull("Password should be null", pass); + + command.setPassword(new String[] {USERNAME_OPTION, USERNAME1, PASSWORD_OPTION, PASSWORD1}); + pass = SecureUserStore.getPassword(USERNAME1); + assertEquals("Password should be null", DigestUtil.encrypt(PASSWORD1), pass); + + command.deleteUser(USERNAME2); + assertFalse("User2 should not exist", SecureUserStore.existsUser(USERNAME2)); + } + + @After + public void cleanUp() { + System.setProperty(USER_STORE_FILE_NAME_PROPERTY, ""); + File file = new File(USER_STORE_FILE_NAME); + if(file.exists()) { + file.delete(); + } + } + + private boolean compareRoles(String expectedRoles, String actualRoles) { + Set<String> expectedRolesSet = new HashSet<String>(); + for(String role : expectedRoles.split(",")) { + expectedRolesSet.add(role); + } + + Set<String> actualRolesSet = new HashSet<String>(); + for(String role : actualRoles.split(",")) { + actualRolesSet.add(role); + } + + return expectedRolesSet.equals(actualRolesSet); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/DigestUtilTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/DigestUtilTests.java new file mode 100755 index 000000000..d1a040b30 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/DigestUtilTests.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.storage; + +import static org.junit.Assert.*; +import java.security.MessageDigest; + +import org.junit.Test; + + +public class DigestUtilTests { + + private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + private static final String MD5 = "MD5"; + private static final String SHA1 = "SHA1"; + private static final String TEXT = "sometext"; + + @Test + public void testEncrypt() throws Exception { + MessageDigest md = MessageDigest.getInstance(MD5); + md.update(TEXT.getBytes()); + byte[] digest = md.digest(); + + char[] chars = new char[2 * digest.length]; + for (int i = 0; i < digest.length; ++i) + { + chars[2 * i] = HEX_CHARS[(digest[i] & 0xF0) >>> 4]; + chars[2 * i + 1] = HEX_CHARS[digest[i] & 0x0F]; + } + + String modifiedText = TEXT + new String(chars); + md = MessageDigest.getInstance(SHA1); + md.update(modifiedText.getBytes()); + digest = md.digest(); + + chars = new char[2 * digest.length]; + for (int i = 0; i < digest.length; ++i) + { + chars[2 * i] = HEX_CHARS[(digest[i] & 0xF0) >>> 4]; + chars[2 * i + 1] = HEX_CHARS[digest[i] & 0x0F]; + } + + String expectedEncryptedText = new String(chars); + + assertEquals("Encrypted text not as expected", expectedEncryptedText, DigestUtil.encrypt(TEXT)); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/SecureUserStoreTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/SecureUserStoreTests.java new file mode 100755 index 000000000..20062e17b --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/storage/SecureUserStoreTests.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.storage; + +import static org.junit.Assert.*; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.equinox.console.jaas.SecureStorageLoginModule; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class SecureUserStoreTests { + + private static final String USER_STORE_FILE_NAME_PROPERTY = "org.eclipse.equinox.console.jaas.file"; + private static final String USER_STORE_FILE_NAME = SecureUserStoreTests.class.getName() + "_store"; + private static final String USERNAME1 = "username1"; + private static final String USERNAME2 = "username2"; + private static final String PASSWORD1 = "password1"; + private static final String PASSWORD2 = "password2"; + private static final String ROLES1 = "role1,role2"; + private static final String ROLES2 = "role3,role4"; + private static final String ROLES_TO_REMOVE = "role2"; + private static final String REMAINING_ROLES = "role1"; + + @Test + public void testStore() throws Exception { + cleanUp(); + + System.setProperty(USER_STORE_FILE_NAME_PROPERTY, USER_STORE_FILE_NAME); + + SecureUserStore.initStorage(); + assertTrue("Secure store file does not exist", new File(USER_STORE_FILE_NAME).exists()); + + SecureUserStore.putUser(USERNAME1, PASSWORD1, null); + SecureUserStore.putUser(USERNAME2, PASSWORD2, ROLES2); + + String[] usernames = SecureUserStore.getUserNames(); + boolean arePresent = (usernames[0].equals(USERNAME1) || usernames[0].equals(USERNAME2)) && (usernames[1].equals(USERNAME1) || usernames[1].equals(USERNAME2)) && (!usernames[0].equals(usernames[1])); + assertTrue("Usernames not correctly saved", arePresent); + + String pass1 = SecureUserStore.getPassword(USERNAME1); + String pass2 = SecureUserStore.getPassword(USERNAME2); + assertTrue("Passwords not correctly saved", pass1.equals(PASSWORD1) && pass2.equals(PASSWORD2)); + + boolean existsUser1 = SecureUserStore.existsUser(USERNAME1); + boolean existsUser2 = SecureUserStore.existsUser(USERNAME2); + assertTrue("Users should exist", existsUser1 && existsUser2); + + String roles = SecureUserStore.getRoles(USERNAME2); + assertEquals("Roles for the second user are not as expected", ROLES2, roles); + + SecureUserStore.addRoles(USERNAME1, ROLES1); + roles = SecureUserStore.getRoles(USERNAME1); + boolean areRolesEqual = compareRoles(ROLES1, roles); + assertTrue("Roles for the first user are not as expected", areRolesEqual); + + SecureUserStore.removeRoles(USERNAME1, ROLES_TO_REMOVE); + roles = SecureUserStore.getRoles(USERNAME1); + areRolesEqual = compareRoles(REMAINING_ROLES, roles); + assertTrue("Roles for the first user are not as expected", areRolesEqual); + + SecureUserStore.resetPassword(USERNAME1); + String pass = SecureUserStore.getPassword(USERNAME1); + assertNull("Password should be null", pass); + + SecureUserStore.setPassword(USERNAME1, PASSWORD1); + pass = SecureUserStore.getPassword(USERNAME1); + assertEquals("Password should be null", PASSWORD1, pass); + + SecureUserStore.deleteUser(USERNAME2); + assertFalse("User2 should not exist", SecureUserStore.existsUser(USERNAME2)); + } + + @After + public void cleanUp() { + System.setProperty(USER_STORE_FILE_NAME_PROPERTY, ""); + File file = new File(USER_STORE_FILE_NAME); + if(file.exists()) { + file.delete(); + } + } + + private boolean compareRoles(String expectedRoles, String actualRoles) { + Set<String> expectedRolesSet = new HashSet<String>(); + for(String role : expectedRoles.split(",")) { + expectedRolesSet.add(role); + } + + Set<String> actualRolesSet = new HashSet<String>(); + for(String role : actualRoles.split(",")) { + actualRolesSet.add(role); + } + + return expectedRolesSet.equals(actualRolesSet); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputHandlerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputHandlerTests.java new file mode 100755 index 000000000..ad4599070 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputHandlerTests.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.equinox.console.supportability; + +import org.eclipse.equinox.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); + byte[] testInput = new byte[] { 'a', 'b', 'c', 'd', 'e', '\r', '\n' }; + byte[] expected = new byte[] { 'a', 'b', 'c', 'd', 'e', '\n' }; + output.write(testInput); + output.flush(); + handler.start(); + + try { + Thread.sleep(WAIT_TIME); + } catch (Exception e) { + // do nothing + } + + byte[] read = new byte[expected.length]; + in.read(read, 0, expected.length); + for (int i = 0; i < expected.length; i++) { + Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]); + } + + output.close(); + input.close(); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputScannerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputScannerTests.java new file mode 100755 index 000000000..2240e2c65 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/supportability/ConsoleInputScannerTests.java @@ -0,0 +1,257 @@ +/******************************************************************************* + * 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.equinox.console.supportability; + +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT320TerminalTypeMappings; +import org.eclipse.equinox.console.completion.common.Completer; +import org.junit.Assert; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +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; + + private static int TAB = 9; + + private static final String COMMANDS = ".commands"; + + @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); + ConsoleInputScanner scanner = new ConsoleInputScanner(in, out); + 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")); + + BundleContext context = createMock(BundleContext.class); + expect(context.getServiceReferences(Completer.class.getName(), null)).andReturn(null).anyTimes(); + replay(context); + + Set<String> commands = new HashSet<String>(); + commands.add("equinox:bundles"); + commands.add("equinox:bundle"); + commands.add("gogo:bundlebylocation"); + commands.add("gogo:bundlelevel"); + commands.add("equinox:headers"); + + CommandSession session = createMock(CommandSession.class); + expect(session.get(COMMANDS)).andReturn(commands).anyTimes(); + replay(session); + + scanner.setContext(context); + scanner.setSession(session); + + scanner.scan(CR); + scanner.scan(LF); + scanner.scan('b'); + scanner.scan('u'); + scanner.scan('n'); + scanner.scan(TAB); + res = byteOut.toString(); + Assert.assertTrue("Expected completion suggestion is not contained in the output", res.contains("bundles\r\n")); + Assert.assertTrue("Expected completion suggestion is not contained in the output", res.contains("bundle\r\n")); + Assert.assertTrue("bun should be completed to bundle", res.endsWith("bundle")); + Assert.assertTrue("Expected completion suggestion is not contained in the output", res.contains("bundlebylocation\r\n")); + Assert.assertTrue("Expected completion suggestion is not contained in the output", res.contains("bundlelevel\r\n")); + Assert.assertFalse("Not expected completion suggestion", res.contains("headers\r\n")); + } + + 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 + 1]; + in.read(read, 0, read.length); + for (int i = 0; i < expected.length; i++) { + Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]); + } + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallbackTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallbackTests.java new file mode 100755 index 000000000..225dfdb09 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallbackTests.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.equinox.console.telnet; + +import org.junit.Assert; +import org.junit.Test; + +public class NegotiationFinishedCallbackTests { + + @Test + public void finishTest() throws Exception { + TelnetConnection telnetConnection = null; + telnetConnection = new TelnetConnection (null, null, null); + NegotiationFinishedCallback callback = new NegotiationFinishedCallback(telnetConnection); + callback.finished(); + Assert.assertTrue("Finished not called on console session", telnetConnection.isTelnetNegotiationFinished); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandTests.java new file mode 100755 index 000000000..b32a55b70 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandTests.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.telnet; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.util.Dictionary; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.junit.Test; +import org.osgi.framework.BundleContext; + + +public class TelnetCommandTests { + + private static final int TEST_CONTENT = 100; + private static final String TELNET_PORT_PROP_NAME = "osgi.console"; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String STOP_COMMAND = "stop"; + private static final String HOST = "localhost"; + private static final String FALSE = "false"; + private static final int TELNET_PORT = 2223; + private static final long WAIT_TIME = 5000; + + @Test + public void testTelnetCommand() throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(new Object()); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(FALSE); + EasyMock.expect(context.getProperty(TELNET_PORT_PROP_NAME)).andReturn(Integer.toString(TELNET_PORT)); + EasyMock.expect(context.registerService((String)EasyMock.anyObject(), EasyMock.anyObject(), (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + TelnetCommand command = new TelnetCommand(processor, context); + command.start(); + + Socket socketClient = null; + try { + socketClient = new Socket(HOST, TELNET_PORT); + 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(); + } + command.telnet(new String[] {STOP_COMMAND}); + } + EasyMock.verify(context); + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandWithConfigAdminTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandWithConfigAdminTests.java new file mode 100755 index 000000000..2eb207297 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetCommandWithConfigAdminTests.java @@ -0,0 +1,196 @@ +package org.eclipse.equinox.console.telnet; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Hashtable; + +import junit.framework.Assert; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.BundleListener; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkListener; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ManagedService; + + +public class TelnetCommandWithConfigAdminTests { + private static final int TEST_CONTENT = 100; + private static final String STOP_COMMAND = "stop"; + private static final String HOST = "localhost"; + private static final String TELNET_PORT = "2223"; + private static final long WAIT_TIME = 5000; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + private ManagedService configurator; + + @Test + public void testTelnetCommandWithConfigAdminEnabledTelnet() throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(new Object()); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + final ServiceRegistration<?> registration = EasyMock.createMock(ServiceRegistration.class); + registration.setProperties((Dictionary)EasyMock.anyObject()); + + EasyMock.expectLastCall(); + EasyMock.replay(registration); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(TRUE); + EasyMock.expect( + (ServiceRegistration) context.registerService( + (String)EasyMock.anyObject(), + (ManagedService)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject()) + ).andAnswer((IAnswer<ServiceRegistration<?>>) new IAnswer<ServiceRegistration<?>>() { + public ServiceRegistration<?> answer() { + configurator = (ManagedService) EasyMock.getCurrentArguments()[1]; + return registration; + } + }); + EasyMock.expect( + context.registerService( + (String)EasyMock.anyObject(), + (TelnetCommand)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + TelnetCommand command = new TelnetCommand(processor, context); + command.start(); + Dictionary props = new Hashtable(); + props.put("port", TELNET_PORT); + props.put("host", HOST); + props.put("enabled", TRUE); + configurator.updated(props); + + Socket socketClient = null; + try { + socketClient = new Socket(HOST, Integer.parseInt(TELNET_PORT)); + 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(); + } + command.telnet(new String[] {STOP_COMMAND}); + } + EasyMock.verify(context); + } + + @Test + public void testTelnetCommandWithConfigAdminDisabledTelnet() throws Exception { + disabledTelnet(false); + } + + @Test + public void testTelnetCommandWithConfigAdminDisabledTelnetByDefault() throws Exception { + disabledTelnet(true); + } + + private void disabledTelnet(boolean isDefault) throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(4); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(new Object()); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + final ServiceRegistration<?> registration = EasyMock.createMock(ServiceRegistration.class); + registration.setProperties((Dictionary)EasyMock.anyObject()); + + EasyMock.expectLastCall(); + EasyMock.replay(registration); + + BundleContext context = EasyMock.createMock(BundleContext.class); + EasyMock.expect(context.getProperty(USE_CONFIG_ADMIN_PROP)).andReturn(TRUE); + EasyMock.expect( + (ServiceRegistration) context.registerService( + (String)EasyMock.anyObject(), + (ManagedService)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject()) + ).andAnswer((IAnswer<ServiceRegistration<?>>) new IAnswer<ServiceRegistration<?>>() { + public ServiceRegistration<?> answer() { + configurator = (ManagedService) EasyMock.getCurrentArguments()[1]; + return registration; + } + }); + EasyMock.expect( + context.registerService( + (String)EasyMock.anyObject(), + (TelnetCommand)EasyMock.anyObject(), + (Dictionary<String, ?>)EasyMock.anyObject())).andReturn(null); + EasyMock.replay(context); + + TelnetCommand command = new TelnetCommand(processor, context); + command.start(); + Dictionary props = new Hashtable(); + props.put("port", TELNET_PORT); + props.put("host", HOST); + if (isDefault == false) { + props.put("enabled", FALSE); + } + configurator.updated(props); + + Socket socketClient = null; + try { + socketClient = new Socket(HOST, Integer.parseInt(TELNET_PORT)); + Assert.fail("It should not be possible to open a socket to " + HOST + ":" + TELNET_PORT); + } catch (IOException e) { + // this is ok, there should be an exception + } finally { + if (socketClient != null) { + socketClient.close(); + } + try { + command.telnet(new String[] {STOP_COMMAND}); + } catch (IllegalStateException e) { + //this is expected + } + } + EasyMock.verify(context); + } + +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetConnectionTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetConnectionTests.java new file mode 100755 index 000000000..c133ead89 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetConnectionTests.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.equinox.console.telnet; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.junit.Assert; +import org.junit.Test; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.common.ConsoleInputStream; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.ServerSocket; +import java.net.Socket; + +public class TelnetConnectionTests { + + private static final String HOST = "localhost"; + private static final int TEST_CONTENT = 100; + private static final int IAC = 255; + + @Test + public void testTelneConnection() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + TelnetConnection connection = null; + OutputStream outClient = null; + OutputStream outServer = null; + + try { + servSocket = new ServerSocket(0); + socketClient = new Socket(HOST, servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream) EasyMock.anyObject(), (PrintStream) EasyMock.anyObject(), (PrintStream) EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + connection = new TelnetConnection(socketServer, processor, null); + connection.start(); + + outClient = socketClient.getOutputStream(); + outClient.write(TEST_CONTENT); + outClient.write('\n'); + outClient.flush(); + + InputStream input = socketServer.getInputStream(); + 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); + connection.telnetNegotiationFinished(); + Thread.sleep(5000); + EasyMock.verify(session, processor); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (outClient != null) { + outClient.close(); + } + if (outServer != null) { + outServer.close(); + } + + if (socketServer != null) { + socketServer.close(); + } + + if (servSocket != null) { + servSocket.close(); + } + } + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetDisconnectionTest.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetDisconnectionTest.java new file mode 100755 index 000000000..810d1f524 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetDisconnectionTest.java @@ -0,0 +1,103 @@ +package org.eclipse.equinox.console.telnet; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintStream; +import java.net.ServerSocket; +import java.net.Socket; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.commands.DisconnectCommand; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.junit.Assert; +import org.junit.Test; +import org.osgi.framework.BundleContext; + +public class TelnetDisconnectionTest { + private static final String HOST = "localhost"; + private InputStream in; + + @Test + public void testTelneConnection() throws Exception { + ServerSocket servSocket = null; + Socket socketClient = null; + Socket socketServer = null; + TelnetConnection connection = null; + OutputStream outClient = null; + OutputStream outServer = null; + + try { + servSocket = new ServerSocket(0); + socketClient = new Socket(HOST, servSocket.getLocalPort()); + socketServer = servSocket.accept(); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + connection = new TelnetConnection(socketServer, processor, null); + + final CommandSession session = EasyMock.createMock(CommandSession.class); + EasyMock.makeThreadSafe(session, true); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.expect(session.get("CLOSEABLE")).andReturn(connection); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(null); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + EasyMock.expect(processor.createSession((ConsoleInputStream) EasyMock.anyObject(), (PrintStream) EasyMock.anyObject(), (PrintStream) EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + connection.start(); + + BundleContext context = EasyMock.createMock(BundleContext.class); + final DisconnectCommand command = new DisconnectCommand(context); + + PipedOutputStream outputStream = new PipedOutputStream(); + PipedInputStream inputStream = new PipedInputStream(outputStream); + + in = System.in; + System.setIn(inputStream); + + new Thread() { + public void run() { + command.disconnect(session); + } + }.start(); + + outputStream.write(new byte[]{'y'}); + outputStream.write('\n'); + outputStream.flush(); + + Thread.sleep(3000); + Assert.assertTrue("Socket is not closed!", socketServer.isClosed()); + + connection.telnetNegotiationFinished(); + Thread.sleep(5000); + EasyMock.verify(session, processor); + } finally { + if (socketClient != null) { + socketClient.close(); + } + if (outClient != null) { + outClient.close(); + } + if (outServer != null) { + outServer.close(); + } + + if (socketServer != null) { + socketServer.close(); + } + + if (servSocket != null) { + servSocket.close(); + } + + System.setIn(in); + } + } +} diff --git a/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetInputHandlerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetInputHandlerTests.java new file mode 100755 index 000000000..f5d3bd6a8 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/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.equinox.console.telnet; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetInputScannerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetInputScannerTests.java new file mode 100755 index 000000000..1a9290484 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetInputScannerTests.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * 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.equinox.console.telnet; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT320TerminalTypeMappings; +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 DO_NOT = 254; + + private static final int TTYPE = 24; + + private static final int WILL = 251; + + private static final int WILL_NOT = 252; + + private static final int SB = 250; + + private static final int SE = 240; + + private static final int EL = 248; + + 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) SE); + scanner.scan((byte) EL); + scanner.scan((byte) SB); + scanner.scan((byte) WILL); + scanner.scan((byte) WILL_NOT); + scanner.scan((byte) DO); + scanner.scan((byte) DO_NOT); + 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 ab, 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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetOutputStreamTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetOutputStreamTests.java new file mode 100755 index 000000000..f975f4dc7 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/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.equinox.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/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetServerTests.java b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetServerTests.java new file mode 100755 index 000000000..21eb4c546 --- /dev/null +++ b/bundles/org.eclipse.equinox.console.tests/src/org/eclipse/equinox/console/telnet/TelnetServerTests.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * 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.equinox.console.telnet; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.easymock.EasyMock; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.junit.Assert; +import org.junit.Test; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.ConnectException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +import static org.easymock.EasyMock.*; + +public class TelnetServerTests { + + private static final String HOST = "localhost"; + private static final int PORT = 38888; + private static final long WAIT_TIME = 5000; + private static final int TEST_CONTENT = 100; + + @Test + public void testTelnetServer() throws Exception { + + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(3); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(new Object()); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + List<CommandProcessor> processors = new ArrayList<CommandProcessor>(); + processors.add(processor); + TelnetServer telnetServer = new TelnetServer(null, processors, HOST, PORT); + telnetServer.start(); + Socket socketClient = null; + + try { + socketClient = new Socket("localhost", PORT); + 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 + } + verify(); + } catch(ConnectException e) { + Assert.fail("Telnet port not open"); + } finally { + if (socketClient != null) { + socketClient.close(); + } + telnetServer.stopTelnetServer(); + } + } + + @Test + public void testTelnetServerWithoutHost() throws Exception { + CommandSession session = EasyMock.createMock(CommandSession.class); + session.put((String)EasyMock.anyObject(), EasyMock.anyObject()); + EasyMock.expectLastCall().times(4); + EasyMock.expect(session.execute((String)EasyMock.anyObject())).andReturn(new Object()); + session.close(); + EasyMock.expectLastCall(); + EasyMock.replay(session); + + CommandProcessor processor = EasyMock.createMock(CommandProcessor.class); + EasyMock.expect(processor.createSession((ConsoleInputStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject(), (PrintStream)EasyMock.anyObject())).andReturn(session); + EasyMock.replay(processor); + + List<CommandProcessor> processors = new ArrayList<CommandProcessor>(); + processors.add(processor); + TelnetServer telnetServer = new TelnetServer(null, processors, null, PORT); + telnetServer.start(); + Socket socketClient = null; + + try { + socketClient = new Socket("localhost", PORT); + 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 + } + } catch(ConnectException e) { + Assert.fail("Telnet port not open"); + } finally { + if (socketClient != null) { + socketClient.close(); + } + telnetServer.stopTelnetServer(); + } + + } + +} diff --git a/bundles/org.eclipse.equinox.console/.classpath b/bundles/org.eclipse.equinox.console/.classpath new file mode 100755 index 000000000..64c5e31b7 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/.classpath @@ -0,0 +1,7 @@ +<?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/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.console/.project b/bundles/org.eclipse.equinox.console/.project new file mode 100755 index 000000000..f5b6451f7 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.console/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 000000000..0c40437fb --- /dev/null +++ b/bundles/org.eclipse.equinox.console/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Thu Jun 24 08:40:05 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/bundles/org.eclipse.equinox.console/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.console/META-INF/MANIFEST.MF new file mode 100755 index 000000000..d744dd60b --- /dev/null +++ b/bundles/org.eclipse.equinox.console/META-INF/MANIFEST.MF @@ -0,0 +1,33 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Console supportability plug-in +Bundle-SymbolicName: org.eclipse.equinox.console +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.equinox.console.command.adapter.Activator +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Import-Package: javax.security.auth;resolution:=optional, + javax.security.auth.callback;resolution:=optional, + javax.security.auth.login;resolution:=optional, + javax.security.auth.spi;resolution:=optional, + org.apache.felix.service.command;status=provisional;version="0.8.0", + org.apache.sshd;version="0.5.0";resolution:=optional, + org.apache.sshd.common;version="0.5.0";resolution:=optional, + org.apache.sshd.server;version="0.5.0";resolution:=optional, + org.apache.sshd.server.jaas;version="0.5.0";resolution:=optional, + org.apache.sshd.server.keyprovider;version="0.5.0";resolution:=optional, + org.apache.sshd.server.session;version="0.5.0";resolution:=optional, + org.apache.sshd.server.shell;version="0.5.0";resolution:=optional, + org.eclipse.osgi.framework.console, + org.eclipse.osgi.service.environment, + org.eclipse.osgi.service.resolver, + org.eclipse.osgi.util, + org.osgi.framework, + org.osgi.service.cm;resolution:=optional, + org.osgi.service.condpermadmin, + org.osgi.service.packageadmin, + org.osgi.service.permissionadmin, + org.osgi.service.startlevel, + org.osgi.util.tracker +Export-Package: org.eclipse.equinox.console.completion.common, + org.eclipse.equinox.console.jaas diff --git a/bundles/org.eclipse.equinox.console/about.html b/bundles/org.eclipse.equinox.console/about.html new file mode 100755 index 000000000..359fab5a4 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/about.html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> +<title>About</title> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>January 10, 2011</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.console/build.properties b/bundles/org.eclipse.equinox.console/build.properties new file mode 100755 index 000000000..929ee1696 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + about.html,\ + . diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/Activator.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/Activator.java new file mode 100755 index 000000000..d6a4b4cb2 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/Activator.java @@ -0,0 +1,344 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 IBM Corporation, 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: + * Thomas Watson, IBM Corporation - initial API and implementation + * Lazar Kirchev, SAP AG - initial API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.command.adapter; + +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.commands.DisconnectCommand; +import org.eclipse.equinox.console.commands.EquinoxCommandProvider; +import org.eclipse.equinox.console.commands.HelpCommand; +import org.eclipse.equinox.console.commands.ManCommand; +import org.eclipse.equinox.console.ssh.SshCommand; +import org.eclipse.equinox.console.telnet.TelnetCommand; +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.eclipse.osgi.framework.console.ConsoleSession; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.service.startlevel.StartLevel; +import org.osgi.service.permissionadmin.PermissionAdmin; +import org.eclipse.osgi.service.resolver.PlatformAdmin; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator implements BundleActivator { + private ServiceTracker<StartLevel, ?> startLevelManagerTracker; + private ServiceTracker<ConditionalPermissionAdmin, ?> condPermAdminTracker; + private ServiceTracker<PermissionAdmin, ?> permissionAdminTracker; + private ServiceTracker<PackageAdmin, PackageAdmin> packageAdminTracker; + private ServiceTracker<PlatformAdmin, ?> platformAdminTracker; + private static boolean isFirstProcessor = true; + private static TelnetCommand telnetConnection = null; + private static SshCommand sshConnection = null; + private static Object telnetLock = new Object(); + private static Object sshLock = new Object(); + private static List<TelnetCommand> telnetConnections = new ArrayList<TelnetCommand>(); + private static List<SshCommand> sshConnections = new ArrayList<SshCommand>(); + + private ServiceTracker<CommandProcessor, ServiceTracker<ConsoleSession, CommandSession>> commandProcessorTracker; + // Tracker for Equinox CommandProviders + private ServiceTracker<CommandProvider, List<ServiceRegistration<?>>> commandProviderTracker; + + private EquinoxCommandProvider equinoxCmdProvider; + + public static class ProcessorCustomizer implements + ServiceTrackerCustomizer<CommandProcessor, ServiceTracker<ConsoleSession, CommandSession>> { + + private final BundleContext context; + + public ProcessorCustomizer(BundleContext context) { + this.context = context; + } + + public ServiceTracker<ConsoleSession, CommandSession> addingService( + ServiceReference<CommandProcessor> reference) { + CommandProcessor processor = context.getService(reference); + if (processor == null) + return null; + + if (isFirstProcessor) { + isFirstProcessor = false; + telnetConnection = new TelnetCommand(processor, context); + telnetConnection.start(); + sshConnection = new SshCommand(processor, context); + sshConnection.start(); + } else { + telnetConnection.addCommandProcessor(processor); + sshConnection.addCommandProcessor(processor); + } + + ServiceTracker<ConsoleSession, CommandSession> tracker = new ServiceTracker<ConsoleSession, CommandSession>(context, ConsoleSession.class, new SessionCustomizer(context, processor)); + tracker.open(); + return tracker; + } + + public void modifiedService( + ServiceReference<CommandProcessor> reference, + ServiceTracker<ConsoleSession, CommandSession> service) { + // nothing + } + + public void removedService( + ServiceReference<CommandProcessor> reference, + ServiceTracker<ConsoleSession, CommandSession> tracker) { + tracker.close(); + CommandProcessor processor = context.getService(reference); + telnetConnection.removeCommandProcessor(processor); + sshConnection.removeCommandProcessor(processor); + } + } + + // Provides support for Equinox ConsoleSessions + public static class SessionCustomizer implements + ServiceTrackerCustomizer<ConsoleSession, CommandSession> { + private final BundleContext context; + final CommandProcessor processor; + + public SessionCustomizer(BundleContext context, CommandProcessor processor) { + this.context = context; + this.processor = processor; + } + + public CommandSession addingService( + ServiceReference<ConsoleSession> reference) { + final ConsoleSession equinoxSession = context.getService(reference); + if (equinoxSession == null) + return null; + PrintStream output = new PrintStream(equinoxSession.getOutput()); + final CommandSession gogoSession = processor.createSession(equinoxSession.getInput(), output, output); + new Thread(new Runnable(){ + public void run() { + try { + gogoSession.put("SCOPE", "equinox:*"); + gogoSession.put("prompt", "osgi> "); + gogoSession.execute("gosh --login --noshutdown"); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + gogoSession.close(); + equinoxSession.close(); + } + } + + }, "Equinox Console Session").start(); + return null; + } + + public void modifiedService(ServiceReference<ConsoleSession> reference, + CommandSession service) { + // nothing + } + + public void removedService(ServiceReference<ConsoleSession> reference, + CommandSession session) { + session.close(); + } + } + + // All commands, provided by an Equinox CommandProvider, are registered as provided by a CommandProviderAdapter. + public class CommandCustomizer implements + ServiceTrackerCustomizer<CommandProvider, List<ServiceRegistration<?>>> { + + private BundleContext context; + public CommandCustomizer(BundleContext context) { + this.context = context; + } + + public List<ServiceRegistration<?>> addingService(ServiceReference<CommandProvider> reference) { + if (reference.getProperty("osgi.command.function") != null) { + // must be a gogo function already; don' track + return null; + } + CommandProvider command = context.getService(reference); + try { + Method[] commandMethods = getCommandMethods(command); + + if (commandMethods.length > 0) { + List<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>(); + registrations.add(context.registerService(Object.class, new CommandProviderAdapter((CommandProvider) command, commandMethods), getAttributes(commandMethods))); + return registrations; + } else { + context.ungetService(reference); + return null; + } + } catch (Exception e) { + context.ungetService(reference); + return null; + } + } + + + public void modifiedService(ServiceReference<CommandProvider> reference, List<ServiceRegistration<?>> service) { + // Nothing to do. + } + + public void removedService(ServiceReference<CommandProvider> reference, List<ServiceRegistration<?>> registrations) { + for (ServiceRegistration<?> serviceRegistration : registrations) { + serviceRegistration.unregister(); + } + } + + } + + public void start(BundleContext context) throws Exception { + commandProviderTracker = new ServiceTracker<CommandProvider, List<ServiceRegistration<?>>>(context, CommandProvider.class.getName(), new CommandCustomizer(context)); + commandProviderTracker.open(); + commandProcessorTracker = new ServiceTracker<CommandProcessor, ServiceTracker<ConsoleSession,CommandSession>>(context, CommandProcessor.class, new ProcessorCustomizer(context)); + commandProcessorTracker.open(); + + condPermAdminTracker = new ServiceTracker<ConditionalPermissionAdmin, Object>(context, ConditionalPermissionAdmin.class.getName(), null); + condPermAdminTracker.open(); + + // grab permission admin + permissionAdminTracker = new ServiceTracker<PermissionAdmin, Object>(context, PermissionAdmin.class.getName(), null); + permissionAdminTracker.open(); + + startLevelManagerTracker = new ServiceTracker<StartLevel, Object>(context, StartLevel.class.getName(), null); + startLevelManagerTracker.open(); + + packageAdminTracker = new ServiceTracker<PackageAdmin, PackageAdmin>(context, PackageAdmin.class, null); + packageAdminTracker.open(); + + platformAdminTracker = new ServiceTracker<PlatformAdmin, Object>(context, PlatformAdmin.class.getName(), null); + platformAdminTracker.open(); + + equinoxCmdProvider = new EquinoxCommandProvider(context, this); + equinoxCmdProvider.start(); + + HelpCommand helpCommand = new HelpCommand(context); + helpCommand.start(); + + ManCommand manCommand = new ManCommand(context); + manCommand.start(); + + DisconnectCommand disconnectCommand = new DisconnectCommand(context); + disconnectCommand.start(); + + startBundle("org.apache.felix.gogo.runtime", true); + startBundle("org.apache.felix.gogo.shell", true); + startBundle("org.apache.felix.gogo.command", false); + } + + private void startBundle(String bsn, boolean required) throws BundleException { + PackageAdmin pa = packageAdminTracker.getService(); + if (pa != null) { + @SuppressWarnings("deprecation") + Bundle[] shells = pa.getBundles(bsn, null); + if (shells != null && shells.length > 0) { + shells[0].start(Bundle.START_TRANSIENT); + } else if (required) { + throw new BundleException("Missing required bundle: " + bsn); + } + } + } + + public StartLevel getStartLevel() { + return (StartLevel) getServiceFromTracker(startLevelManagerTracker, StartLevel.class.getName()); + } + + public PermissionAdmin getPermissionAdmin() { + return (PermissionAdmin) getServiceFromTracker(permissionAdminTracker, PermissionAdmin.class.getName()); + } + + public ConditionalPermissionAdmin getConditionalPermissionAdmin() { + return (ConditionalPermissionAdmin) getServiceFromTracker(condPermAdminTracker, ConditionalPermissionAdmin.class.getName()); + } + + public PackageAdmin getPackageAdmin() { + return (PackageAdmin) getServiceFromTracker(packageAdminTracker, PackageAdmin.class.getName()); + } + + public PlatformAdmin getPlatformAdmin() { + return (PlatformAdmin) getServiceFromTracker(platformAdminTracker, PlatformAdmin.class.getName()); + } + + private static Object getServiceFromTracker(ServiceTracker<?, ?> tracker, String serviceClass) { + if (tracker == null) + throw new IllegalStateException("Missing service: " + serviceClass); + Object result = tracker.getService(); + if (result == null) + throw new IllegalStateException("Missing service: " + serviceClass); + return result; + } + + Method[] getCommandMethods(Object command) { + ArrayList<Method> names = new ArrayList<Method>(); + Class<?> c = command.getClass(); + Method[] methods = c.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().startsWith("_") + && method.getModifiers() == Modifier.PUBLIC && !method.getName().equals("_help")) { + Type[] types = method.getGenericParameterTypes(); + if (types.length == 1 + && types[0].equals(CommandInterpreter.class)) { + names.add(method); + } + } + } + return names.toArray(new Method[names.size()]); + } + + Dictionary<String, Object> getAttributes(Method[] commandMethods) { + Dictionary<String, Object> dict = new Hashtable<String, Object>(); + dict.put("osgi.command.scope", "equinox"); + String[] methodNames = new String[commandMethods.length]; + for (int i = 0; i < commandMethods.length; i++) { + String methodName = "" + commandMethods[i].getName().substring(1); + methodNames[i] = methodName; + } + + dict.put("osgi.command.function", methodNames); + return dict; + } + + public void stop(BundleContext context) throws Exception { + commandProviderTracker.close(); + commandProcessorTracker.close(); + if (equinoxCmdProvider != null) { + equinoxCmdProvider.stop(); + } + + try { + telnetConnection.telnet(new String[]{"stop"}); + } catch (Exception e) { + // expected if the telnet server is not started + } + + try { + sshConnection.ssh(new String[]{"stop"}); + } catch (Exception e) { + // expected if the ssh server is not started + } + + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapter.java new file mode 100755 index 000000000..08de8e80c --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CommandProviderAdapter.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 IBM Corporation, 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: + * Thomas Watson, IBM Corporation - initial API and implementation + * Lazar Kirchev, SAP AG - initial API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.command.adapter; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import org.eclipse.osgi.framework.console.CommandProvider; + +/** + * This adapter class provides for execution of legacy Equinox commands from + * the Gogo shell. The commands are executed through the main method of the + * adapter. It finds the appropriate Equinox command and executes + * it with the proper argument. + * + */ +public class CommandProviderAdapter { + + private final CommandProvider commandProvider; + private final Method[] commands; + + + public CommandProviderAdapter(CommandProvider commandProvider, Method[] commands) { + this.commandProvider = commandProvider; + this.commands = commands; + } + + public Object main(Object[] args) throws Exception { + try { + // first argument is the command + Method command = findCommand("_" + args[0]); + ArrayList<Object> argList = new ArrayList<Object>(); + for (int i = 1; i < args.length; i++) + argList.add(args[i]); + return command.invoke(commandProvider, new CustomCommandInterpreter(argList)); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof Exception) + throw (Exception) e.getTargetException(); + throw (Error) e.getTargetException(); + } + } + + private Method findCommand(Object commandName) { + for (Method command : commands) { + if (command.getName().equals(commandName)) + return command; + } + throw new IllegalArgumentException("Cannot find the command method for: " + commandName); + } + + // TODO Felix gogo seems to search for _main + public Object _main(Object[] args) throws Exception { + return main(args); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CustomCommandInterpreter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CustomCommandInterpreter.java new file mode 100755 index 000000000..c401e361b --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/command/adapter/CustomCommandInterpreter.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Lazar Kirchev, SAP AG - derivative implementation from FrameworkCommandInterpreter + *******************************************************************************/ +package org.eclipse.equinox.console.command.adapter; + +import java.io.*; +import java.lang.reflect.*; +import java.net.URL; +import java.util.*; +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.osgi.framework.Bundle; + +/** + * A CommandInterpreter to be passed to the legacy Equinox commands, + * executed by the CommandProviderAdapter. + * + */ +public class CustomCommandInterpreter implements CommandInterpreter { + private PrintStream out = System.out; + /** Strings used to format other strings */ + private String tab = "\t"; //$NON-NLS-1$ + private String newline = "\r\n"; //$NON-NLS-1$ + private final Iterator<Object> arguments; + /** + * The maximum number of lines to print without user prompt. + * 0 means no user prompt is required, the window is scrollable. + */ + protected static int maxLineCount; + + /** The number of lines printed without user prompt.*/ + protected int currentLineCount; + + public CustomCommandInterpreter(List<Object> args) { + arguments = args.iterator(); + } + + public Object execute(String cmd) { + return null; + } + + public String nextArgument() { + if (arguments.hasNext()) { + Object next = arguments.next(); + return next == null ? null : next.toString(); + } + return null; + } + + /** + * Prints an object to the outputstream + * + * @param o the object to be printed + */ + public void print(Object o) { + check4More(); + out.print(o); + out.flush(); + } + + /** + * Prints a empty line to the outputstream + */ + public void println() { + println(""); //$NON-NLS-1$ + } + + /** + * Print a stack trace including nested exceptions. + * @param t The offending exception + */ + public void printStackTrace(Throwable t) { + t.printStackTrace(out); + + Method[] methods = t.getClass().getMethods(); + + int size = methods.length; + Class<Throwable> throwable = Throwable.class; + + for (int i = 0; i < size; i++) { + Method method = methods[i]; + + if (Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("get") && throwable.isAssignableFrom(method.getReturnType()) && (method.getParameterTypes().length == 0)) { //$NON-NLS-1$ + try { + Throwable nested = (Throwable) method.invoke(t, (Object) null); + + if ((nested != null) && (nested != t)) { + out.println("Nested Exception"); + printStackTrace(nested); + } + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + } + } + } + + /** + * Prints an object to the output medium (appended with newline character). + * <p> + * If running on the target environment, the user is prompted with '--more' + * if more than the configured number of lines have been printed without user prompt. + * This enables the user of the program to have control over scrolling. + * <p> + * For this to work properly you should not embed "\n" etc. into the string. + * + * @param o the object to be printed + */ + public void println(Object o) { + if (o == null) { + return; + } + synchronized (out) { + check4More(); + printline(o); + currentLineCount++; + currentLineCount += o.toString().length() / 80; + } + } + + /** + * Prints a string to the output medium (appended with newline character). + * <p> + * This method does not increment the line counter for the 'more' prompt. + * + * @param o the string to be printed + */ + private void printline(Object o) { + print(o + newline); + } + + /** + * Prints the given dictionary sorted by keys. + * + * @param dic the dictionary to print + * @param title the header to print above the key/value pairs + */ + public void printDictionary(Dictionary<?,?> dic, String title) { + if (dic == null) + return; + + int count = dic.size(); + String[] keys = new String[count]; + Enumeration<?> keysEnum = dic.keys(); + int i = 0; + while (keysEnum.hasMoreElements()) { + keys[i++] = (String) keysEnum.nextElement(); + } + Arrays.sort(keys); + + if (title != null) { + println(title); + } + for (i = 0; i < count; i++) { + println(" " + keys[i] + " = " + dic.get(keys[i])); //$NON-NLS-1$//$NON-NLS-2$ + } + println(); + } + + /** + * Prints the given bundle resource if it exists + * + * @param bundle the bundle containing the resource + * @param resource the resource to print + */ + public void printBundleResource(Bundle bundle, String resource) { + URL entry = null; + entry = bundle.getEntry(resource); + if (entry != null) { + try { + println(resource); + InputStream in = entry.openStream(); + byte[] buffer = new byte[1024]; + int read = 0; + try { + while ((read = in.read(buffer)) != -1) + print(new String(buffer, 0, read)); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + } catch (Exception e) { + System.err.println(e); + } + } else { + println("CONSOLE_RESOURCE ["+resource+"] NOT_IN_BUNDLE " + bundle.toString()); + } + } + + /** + * Answers the number of lines output to the console + * window should scroll without user interaction. + * + * @return The number of lines to scroll. + */ + private int getMaximumLinesToScroll() { + return maxLineCount; + } + + /** + * Displays the more... prompt if the max line count has been reached + * and waits for the operator to hit enter. + * + */ + private void check4More() { + int max = getMaximumLinesToScroll(); + if (max > 0) { + if (currentLineCount >= max) { + out.print("-- More...Press Enter to Continue..."); + out.flush(); + try { + System.in.read(); + } catch (IOException e) { + e.printStackTrace(); + } // wait for user entry + resetLineCount(); //Reset the line counter for the 'more' prompt + } + } + } + + /** + * Resets the line counter for the 'more' prompt. + */ + private void resetLineCount() { + currentLineCount = 0; + } + + /** + Answer a string (may be as many lines as you like) with help + texts that explain the command. + */ + public String getHelp() { + StringBuffer help = new StringBuffer(256); + help.append("---Controlling the Console---"); + help.append(newline); + help.append(tab); + help.append("more - "); //$NON-NLS-1$ + help.append("More prompt for console output"); + help.append(newline); + help.append(tab); + help.append("disconnect - "); //$NON-NLS-1$ + help.append("isconnects from telnet session"); + help.append(newline); + return help.toString(); + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMessages.properties b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMessages.properties new file mode 100755 index 000000000..99c7f4298 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMessages.properties @@ -0,0 +1,78 @@ +############################################################################### +# Copyright (c) 2003, 2011 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +# Lazar Kirchev, SAP AG - derivative implementation +############################################################################### + +#External Messages for EN locale + +CONSOLE_ID=id +CONSOLE_INVALID_INPUT=Invalid input. +CONSOLE_NO_BUNDLE_SPECIFIED_ERROR=No bundle(s) specified! +CONSOLE_NO_INSTALLED_BUNDLES_ERROR=No installed bundles. +CONSOLE_BUNDLE_ID_MESSAGE=Bundle id is +CONSOLE_ID_MESSAGE=Id={0} +CONSOLE_NO_EXPORTED_PACKAGES_MESSAGE=No exported packages +CONSOLE_NO_IMPORTED_PACKAGES_MESSAGE=No imported packages +CONSOLE_REMOVAL_PENDING_MESSAGE=removal pending +CONSOLE_IMPORTS_MESSAGE=imports +CONSOLE_STALE_MESSAGE=stale +CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE=No exported packages [PackageAdmin service is not registered] +CONSOLE_NO_EXPORTED_PACKAGES_NO_PLATFORM_ADMIN_MESSAGE=No exported packages [PlatformAdmin service is not registered] +CONSOLE_SERVICES_IN_USE_MESSAGE=Services in use: +CONSOLE_NO_SERVICES_IN_USE_MESSAGE=No services in use. +CONSOLE_STATUS_MESSAGE=Status={0} +CONSOLE_DATA_ROOT_MESSAGE=Data Root={0} +CONSOLE_EXPORTED_PACKAGES_MESSAGE=Exported packages +CONSOLE_IMPORTED_PACKAGES_MESSAGE=Imported packages +CONSOLE_EXPORTED_REMOVAL_PENDING_MESSAGE=[exported(removal pending)] +CONSOLE_EXPORTED_MESSAGE=[exported] +CONSOLE_TOTAL_MEMORY_MESSAGE=Total memory: +CONSOLE_FREE_MEMORY_BEFORE_GARBAGE_COLLECTION_MESSAGE=Free memory before GC: +CONSOLE_FREE_MEMORY_AFTER_GARBAGE_COLLECTION_MESSAGE=Free memory after GC: +CONSOLE_MEMORY_GAINED_WITH_GARBAGE_COLLECTION_MESSAGE=Memory gained with GC: +CONSOLE_FRAMEWORK_LAUNCHED_PLEASE_SHUTDOWN_MESSAGE=Framework is launched. Please shutdown framework first. +CONSOLE_CAN_NOT_REFRESH_NO_PACKAGE_ADMIN_ERROR=Cannot refresh [PackageAdmin service is not registered] +CONSOLE_NO_COMMAND_SPECIFIED_ERROR=No command specified +CONSOLE_EXECUTED_RESULT_CODE_MESSAGE=Executed ({0}); result code = {1} +CONSOLE_STARTED_IN_MESSAGE=Started({0}) in {1} +CONSOLE_BUNDLE_HEADERS_TITLE=Bundle headers: +CONSOLE_SYSTEM_PROPERTIES_TITLE=System properties: +CONSOLE_NO_PARAMETERS_SPECIFIED_TITLE=No parameters specified: +CONSOLE_SETTING_PROPERTIES_TITLE=Setting Properties: +CONSOLE_STATE_BUNDLE_TITLE=State Bundle +CONSOLE_THREADGROUP_TITLE=ThreadGroupType: Name: ParentGroup: MaxP: Threads: +CONSOLE_THREADTYPE_TITLE=ThreadType: Name: ThreadGroup: Prio: +STARTLEVEL_FRAMEWORK_ACTIVE_STARTLEVEL=Framework Active Start Level = {0} +STARTLEVEL_NO_STARTLEVEL_GIVEN=No Start Level given. +STARTLEVEL_NO_STARTLEVEL_OR_BUNDLE_GIVEN=No Bundle or Start Level given. +STARTLEVEL_INITIAL_BUNDLE_STARTLEVEL=Initial Bundle Start Level = {0} +STARTLEVEL_BUNDLE_STARTLEVEL=Bundle {0} Start Level = {1} +STARTLEVEL_POSITIVE_INTEGER=Startlevel must be a positive integer. +CONSOLE_NAMED_CLASS_SPACE_MESSAGE=Named class space +CONSOLE_PROVIDED_MESSAGE=[provided] +CONSOLE_HOST_MESSAGE=Host bundles +CONSOLE_NO_HOST_MESSAGE=No host bundles +CONSOLE_FRAGMENT_MESSAGE=Fragment bundles +CONSOLE_NO_FRAGMENT_MESSAGE=No fragment bundles +CONSOLE_NO_NAMED_CLASS_SPACES_MESSAGE=No named class spaces +CONSOLE_REQUIRED_BUNDLES_MESSAGE=Required bundles +CONSOLE_NO_REQUIRED_BUNDLES_MESSAGE=No required bundles +CONSOLE_REQUIRES_MESSAGE=[requires] +CONSOLE_CANNOT_ACCESS_SYSTEM_PROPERTIES=Cannot set framework property +CONSOLE_NOTHING_TO_INSTALL_ERROR=Nothing to install +CONSOLE_REGISTERED_BY_BUNDLE_MESSAGE = "Registered by bundle:" +CONSOLE_BUNDLES_USING_SERVICE_MESSAGE = "Bundles using service" +CONSOLE_NO_BUNDLES_USING_SERVICE_MESSAGE = "No bundles using service." +CONSOLE_NO_REGISTERED_SERVICES_MESSAGE = "No registered services." +CONSOLE_FRAMEWORK_IS_LAUNCHED_MESSAGE = "Framework is launched." +CONSOLE_FRAMEWORK_IS_SHUTDOWN_MESSAGE = "Framework is shutdown." +CONSOLE_BUNDLE_LOCATION_MESSAGE = "Bundle Location" +CONSOLE_STATE_BUNDLE_FILE_NAME_HEADER = "State Bundle File Name" +CONSOLE_REGISTERED_SERVICES_MESSAGE = "Registered Services"
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMsg.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMsg.java new file mode 100755 index 000000000..e58b82df6 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ConsoleMsg.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2004, 20011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Lazar Kirchev, SAP AG - derivative implementation + *******************************************************************************/ +package org.eclipse.equinox.console.commands; + +import org.eclipse.osgi.util.NLS; + +public class ConsoleMsg extends NLS { + public static final String BUNDLE_NAME = "org.eclipse.equinox.console.commands.ConsoleMessages"; //$NON-NLS-1$ + + public static String CONSOLE_INVALID_INPUT; + public static String CONSOLE_NO_BUNDLE_SPECIFIED_ERROR; + public static String CONSOLE_NOTHING_TO_INSTALL_ERROR; + public static String CONSOLE_BUNDLE_ID_MESSAGE; + public static String CONSOLE_NO_INSTALLED_BUNDLES_ERROR; + public static String CONSOLE_REGISTERED_SERVICES_MESSAGE; + public static String CONSOLE_FRAMEWORK_IS_LAUNCHED_MESSAGE; + public static String CONSOLE_FRAMEWORK_IS_SHUTDOWN_MESSAGE; + public static String CONSOLE_ID; + public static String CONSOLE_BUNDLE_LOCATION_MESSAGE; + public static String CONSOLE_STATE_BUNDLE_FILE_NAME_HEADER; + public static String CONSOLE_BUNDLES_USING_SERVICE_MESSAGE; + public static String CONSOLE_NO_REGISTERED_SERVICES_MESSAGE; + public static String CONSOLE_NO_BUNDLES_USING_SERVICE_MESSAGE; + public static String CONSOLE_REGISTERED_BY_BUNDLE_MESSAGE; + public static String CONSOLE_IMPORTS_MESSAGE; + public static String CONSOLE_STALE_MESSAGE; + public static String CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE; + public static String CONSOLE_NO_EXPORTED_PACKAGES_NO_PLATFORM_ADMIN_MESSAGE; + public static String CONSOLE_NO_EXPORTED_PACKAGES_MESSAGE; + public static String CONSOLE_REMOVAL_PENDING_MESSAGE; + public static String CONSOLE_SERVICES_IN_USE_MESSAGE; + public static String CONSOLE_NO_SERVICES_IN_USE_MESSAGE; + public static String CONSOLE_ID_MESSAGE; + public static String CONSOLE_STATUS_MESSAGE; + public static String CONSOLE_DATA_ROOT_MESSAGE; + + public static String CONSOLE_IMPORTED_PACKAGES_MESSAGE; + public static String CONSOLE_NO_IMPORTED_PACKAGES_MESSAGE; + public static String CONSOLE_HOST_MESSAGE; + public static String CONSOLE_EXPORTED_PACKAGES_MESSAGE; + public static String CONSOLE_EXPORTED_REMOVAL_PENDING_MESSAGE; + public static String CONSOLE_EXPORTED_MESSAGE; + public static String CONSOLE_NO_HOST_MESSAGE; + public static String CONSOLE_FRAGMENT_MESSAGE; + public static String CONSOLE_NO_FRAGMENT_MESSAGE; + public static String CONSOLE_NO_NAMED_CLASS_SPACES_MESSAGE; + public static String CONSOLE_NAMED_CLASS_SPACE_MESSAGE; + public static String CONSOLE_PROVIDED_MESSAGE; + public static String CONSOLE_REQUIRED_BUNDLES_MESSAGE; + public static String CONSOLE_NO_REQUIRED_BUNDLES_MESSAGE; + public static String CONSOLE_TOTAL_MEMORY_MESSAGE; + public static String CONSOLE_FREE_MEMORY_BEFORE_GARBAGE_COLLECTION_MESSAGE; + public static String CONSOLE_FREE_MEMORY_AFTER_GARBAGE_COLLECTION_MESSAGE; + public static String CONSOLE_MEMORY_GAINED_WITH_GARBAGE_COLLECTION_MESSAGE; + public static String CONSOLE_FRAMEWORK_LAUNCHED_PLEASE_SHUTDOWN_MESSAGE; + public static String CONSOLE_CAN_NOT_REFRESH_NO_PACKAGE_ADMIN_ERROR; + public static String CONSOLE_NO_COMMAND_SPECIFIED_ERROR; + public static String CONSOLE_STARTED_IN_MESSAGE; + public static String CONSOLE_EXECUTED_RESULT_CODE_MESSAGE; + public static String CONSOLE_BUNDLE_HEADERS_TITLE; + public static String CONSOLE_SYSTEM_PROPERTIES_TITLE; + public static String CONSOLE_NO_PARAMETERS_SPECIFIED_TITLE; + public static String CONSOLE_SETTING_PROPERTIES_TITLE; + public static String CONSOLE_STATE_BUNDLE_TITLE; + public static String CONSOLE_THREADGROUP_TITLE; + public static String CONSOLE_THREADTYPE_TITLE; + public static String CONSOLE_REQUIRES_MESSAGE; + public static String CONSOLE_CANNOT_ACCESS_SYSTEM_PROPERTIES; + + public static String STARTLEVEL_FRAMEWORK_ACTIVE_STARTLEVEL; + public static String STARTLEVEL_BUNDLE_STARTLEVEL; + public static String STARTLEVEL_NO_STARTLEVEL_GIVEN; + public static String STARTLEVEL_NO_STARTLEVEL_OR_BUNDLE_GIVEN; + public static String STARTLEVEL_INITIAL_BUNDLE_STARTLEVEL; + public static String STARTLEVEL_POSITIVE_INTEGER; + + public static final String CONSOLE_HELP_EXIT_COMMAND_DESCRIPTION = "exit immediately (System.exit)"; + public static final String CONSOLE_HELP_LAUNCH_COMMAND_DESCRIPTION = "start the OSGi Framework"; + public static final String CONSOLE_HELP_SHUTDOWN_COMMAND_DESCRIPTION = "shutdown the OSGi Framework"; + public static final String CONSOLE_HELP_START_COMMAND_DESCRIPTION = "start the specified bundle(s)"; + public static final String CONSOLE_HELP_START_COMMAND_ARGUMENT_DESCRIPTION = "bundle(s) to start"; + public static final String CONSOLE_HELP_STOP_COMMAND_DESCRIPTION = "stop the specified bundle(s)"; + public static final String CONSOLE_HELP_STOP_COMMAND_ARGUMENT_DESCRIPTION = "bundle(s) to stop"; + public static final String CONSOLE_HELP_INSTALL_COMMAND_DESCRIPTION = "install and optionally start bundle from the given URL"; + public static final String CONSOLE_HELP_INSTALL_START_OPTION_DESCRIPTION = "spedify if the bundle should be started after installation"; + public static final String CONSOLE_HELP_INSTALL_START_ARGUMENT_DESCRIPTION = "Location of bundle to install"; + public static final String CONSOLE_HELP_UPDATE_COMMAND_DESCRIPTION = "update the specified bundle(s)"; + public static final String CONSOLE_HELP_UPDATE_COMMAND_ARGUMENT_DESCRIPTION = "bundle(s) to update"; + public static final String CONSOLE_HELP_UPDATE_SOURCE_COMMAND_DESCRIPTION = "Update the specified bundle from the specified location"; + public static final String CONSOLE_HELP_UPDATE_SOURCE_COMMAND_BUNDLE_ARGUMENT_DESCRIPTION = "Bundle to update"; + public static final String CONSOLE_HELP_UPDATE_SOURCE_COMMAND_URL_ARGUMENT_DESCRIPTION = "Location of the new bundle content"; + public static final String CONSOLE_HELP_UNINSTALL_COMMAND_DESCRIPTION = "uninstall the specified bundle(s)"; + public static final String CONSOLE_HELP_UNINSTALL_COMMAND_ARGUMENT_DESCRIPTION = "bundle(s) to uninstall"; + public static final String CONSOLE_HELP_STATUS_COMMAND_DESCRIPTION = "display installed bundles and registered services"; + public static final String CONSOLE_HELP_STATUS_ARGUMENT_DESCRIPTION = "[-s <comma separated list of bundle states>] [segment of bsn]"; + public static final String CONSOLE_HELP_FILTER_ARGUMENT_DESCRIPTION = "Optional filter for filtering the displayed services. Examples for the filter: (objectClass=com.xyz.Person); (&(objectClass=com.xyz.Person)(sn=Jensen)); passing only com.xyz.Person is a shortcut for (objectClass=com.xyz.Person). The filter syntax specification is available at http://www.ietf.org/rfc/rfc1960.txt"; + public static final String CONSOLE_HELP_SERVICES_COMMAND_DESCRIPTION = "display registered service details. Examples for [filter]: (objectClass=com.xyz.Person); (&(objectClass=com.xyz.Person)(sn=Jensen)); passing only com.xyz.Person is a shortcut for (objectClass=com.xyz.Person). The filter syntax specification is available at http://www.ietf.org/rfc/rfc1960.txt"; + public static final String CONSOLE_HELP_PACKAGES_BUNDLE_ARGUMENT_DESCRIPTION = "Bundle whose packages to display. If not present displays all exported packages"; + public static final String CONSOLE_HELP_PACKAGES_PACKAGE_ARGUMENT_DESCRIPTION = "Package name of the package to display"; + public static final String CONSOLE_HELP_PACKAGES_COMMAND_DESCRIPTION = "display imported/exported package details"; + public static final String CONSOLE_HELP_BUNDLES_COMMAND_DESCRIPTION = "display details for all installed bundles"; + public static final String CONSOLE_HELP_IDLOCATION_ARGUMENT_DESCRIPTION = "(<id>|<location>)"; + public static final String CONSOLE_HELP_BUNDLE_COMMAND_DESCRIPTION = "display details for the specified bundle(s)"; + public static final String CONSOLE_HELP_GC_COMMAND_DESCRIPTION = "perform a garbage collection"; + public static final String CONSOLE_HELP_INIT_COMMAND_DESCRIPTION = "uninstall all bundles"; + public static final String CONSOLE_HELP_CLOSE_COMMAND_DESCRIPTION = "shutdown and exit"; + public static final String CONSOLE_HELP_REFRESH_COMMAND_DESCRIPTION = "refresh the packages of the specified bundles"; + public static final String CONSOLE_HELP_REFRESH_COMMAND_ARGUMENT_DESCRIPTION = "list of bundles whose packages to be refreshed; if not present refreshes all bundles"; + public static final String CONSOLE_HELP_EXEC_COMMAND_DESCRIPTION = "execute a command in a separate process and wait"; + public static final String CONSOLE_HELP_EXEC_COMMAND_ARGUMENT_DESCRIPTION = "command to be executed"; + public static final String CONSOLE_HELP_FORK_COMMAND_DESCRIPTION = "execute a command in a separate process"; + public static final String CONSOLE_HELP_FORK_COMMAND_ARGUMENT_DESCRIPTION = "command to be executed"; + public static final String CONSOLE_HELP_HEADERS_COMMAND_DESCRIPTION = "print bundle headers"; + public static final String CONSOLE_HELP_HEADERS_COMMAND_ARGUMENT_DESCRIPTION = "bundles to print headers for"; + public static final String CONSOLE_PROPS_COMMAND_DESCRIPTION = "Display system properties"; + public static final String CONSOLE_HELP_SETPROP_COMMAND_DESCRIPTION = "set OSGi properties"; + public static final String CONSOLE_HELP_SETPROP_COMMAND_ARGUMENTS_DESCRIPTION = "list of properties with values to be set; the format is <key>=<value> and the pairs are separated with space if more than one"; + public static final String CONSOLE_HELP_SS_COMMAND_DESCRIPTION = "display installed bundles (short status)"; + public static final String CONSOLE_THREADS_COMMAND_DESCRIPTION = "display threads and thread groups"; + public static final String CONSOLE_HELP_SL_COMMAND_DESCRIPTION = "display the start level for the specified bundle, or for the framework if no bundle specified"; + public static final String CONSOLE_HELP_SL_COMMAND_ARGUMENT_DESCRIPTION = "bundle to get the start level"; + public static final String CONSOLE_HELP_SETFWSL_COMMAND_DESCRIPTION = "set the framework start level"; + public static final String CONSOLE_HELP_SETFWSL_COMMAND_ARGUMENT_DESCRIPTION = "new start level"; + public static final String CONSOLE_HELP_SETBSL_COMMAND_DESCRIPTION = "set the start level for the bundle(s)"; + public static final String CONSOLE_HELP_SETBSL_COMMAND_ARGUMENT_DESCRIPTION = "bundle(s) to change startlevel"; + public static final String CONSOLE_HELP_SETIBSL_COMMAND_DESCRIPTION = "set the initial bundle start level"; + public static final String CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_DESCRIPTION = "lists required bundles having the specified symbolic name"; + public static final String CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_ARGUMENT_DESCRIPTION = "symbolic name for required bundles to be listed; if not specified all required bundles will be listed"; + public static final String CONSOLE_HELP_PROFILELOG_COMMAND_DESCRIPTION = "Display & flush the profile log messages"; + public static final String CONSOLE_HELP_VISIBLE_PACKAGES_COMMAND_DESCRIPTION = "lists all packages visible from the specified bundle"; + public static final String CONSOLE_HELP_VISIBLE_PACKAGES_COMMAND_ARGUMENTS_DESCRIPTION = "bundle to list the visible packages"; + public static final String CONSOLE_HELP_GETPROP_COMMAND_DESCRIPTION = "displays the system properties with the given name, or all of them"; + public static final String CONSOLE_HELP_GETPROP_COMMAND_ARGUMENT_DESCRIPTION = "name of system property to dispaly"; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, ConsoleMsg.class); + } +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/DisconnectCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/DisconnectCommand.java new file mode 100755 index 000000000..69acc779c --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/DisconnectCommand.java @@ -0,0 +1,63 @@ +package org.eclipse.equinox.console.commands; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import java.io.Closeable; +import org.osgi.framework.BundleContext; + +/** + * This class implements functionality to disconnect from telnet or ssh console. + */ +public class DisconnectCommand { + private static final String CLOSEABLE = "CLOSEABLE"; + private static final String DISCONNECT_MESSAGE = "Disconnect from console? (y/n; default=y) "; + private static final String DISCONNECT_CONFIRMATION_Y = "y"; + + private BundleContext context; + + public DisconnectCommand(BundleContext context) { + this.context = context; + } + + public void start() { + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(CommandProcessor.COMMAND_SCOPE, "equinox"); + props.put(CommandProcessor.COMMAND_FUNCTION, new String[] {"disconnect"}); + context.registerService(DisconnectCommand.class.getName(), this, props); + } + + public void disconnect(CommandSession session) { + System.out.print(DISCONNECT_MESSAGE); + System.out.flush(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String reply = null; + try { + reply = reader.readLine(); + } catch (IOException e) { + System.out.println("Error while reading confirmation"); + } + + if (reply != null) { + if (reply.toLowerCase().startsWith(DISCONNECT_CONFIRMATION_Y) || reply.length() == 0) { + Closeable closable = (Closeable)session.get(CLOSEABLE); + if (closable != null) { + try { + closable.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + + session.close(); + } + } + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandProvider.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandProvider.java new file mode 100755 index 000000000..b6d452751 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandProvider.java @@ -0,0 +1,1819 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Lazar Kirchev, SAP AG - derivative implementation to migrate the commands from FrameworkCommandProvider to Gogo shell commands + *******************************************************************************/ + +package org.eclipse.equinox.console.commands; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.TreeSet; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.Converter; +import org.apache.felix.service.command.Descriptor; +import org.apache.felix.service.command.Parameter; +import org.eclipse.equinox.console.command.adapter.Activator; +import org.eclipse.osgi.service.environment.EnvironmentInfo; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.DisabledInfo; +import org.eclipse.osgi.service.resolver.ExportPackageDescription; +import org.eclipse.osgi.service.resolver.ImportPackageSpecification; +import org.eclipse.osgi.service.resolver.PlatformAdmin; +import org.eclipse.osgi.service.resolver.State; +import org.eclipse.osgi.service.resolver.StateHelper; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.SynchronousBundleListener; +import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; +import org.osgi.service.condpermadmin.ConditionalPermissionInfo; +import org.osgi.service.condpermadmin.ConditionalPermissionUpdate; +import org.osgi.service.packageadmin.ExportedPackage; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.service.packageadmin.RequiredBundle; +import org.osgi.service.permissionadmin.PermissionAdmin; +import org.osgi.service.startlevel.StartLevel; + +/** + * This class provides methods to execute commands from the command line. It registers + * itself as a service so it can be invoked by a CommandProcessor. + * + * The commands provided by this class are: + ---Controlling the OSGi framework--- + close - shutdown and exit + exit - exit immediately (System.exit) + gc - perform a garbage collection + init - uninstall all bundles + launch - start the Service Management Framework + setprop <key>=<value> - set the OSGI property + shutdown - shutdown the Service Management Framework + ---Controlliing Bundles--- + install <url> {s[tart]} - install and optionally start bundle from the given URL + refresh (<id>|<location>) - refresh the packages of the specified bundles + start (<id>|<location>) - start the specified bundle(s) + stop (<id>|<location>) - stop the specified bundle(s) + uninstall (<id>|<location>) - uninstall the specified bundle(s) + update (<id>|<location>|<*>) - update the specified bundle(s) + ---Displaying Status--- + bundle (<id>|<location>) - display details for the specified bundle(s) + bundles - display details for all installed bundles + headers (<id>|<location>) - print bundle headers + packages {<pkgname>|<id>|<location>} - display imported/exported package details + props - display System properties + services {filter} - display registered service details. Examples for [filter]: (objectClass=com.xyz.Person); (&(objectClass=com.xyz.Person)(|(sn=Jensen)(cn=Babs J*))); passing only com.xyz.Person is a shortcut for (objectClass=com.xyz.Person). The filter syntax specification is available at http://www.ietf.org/rfc/rfc1960.txt + ss - display installed bundles (short status) + status - display installed bundles and registered services + threads - display threads and thread groups + ---Extras--- + exec <command> - execute a command in a separate process and wait + fork <command> - execute a command in a separate process + getprop <name> - Displays the system properties with the given name, or all of them. + requiredBundles [<bsn>] - lists required bundles having the specified symbolic name or all if no bsn is specified + classSpaces [<bsn>] - lists required bundles having the specified symbolic name or all if no bsn is specified + ---Controlling StartLevel--- + sl {(<id>|<location>)} - display the start level for the specified bundle, or for the framework if no bundle specified + setfwsl <start level> - set the framework start level + setbsl <start level> (<id>|<location>) - set the start level for the bundle(s) + setibsl <start level> - set the initial bundle start level + +*/ + +public class EquinoxCommandProvider implements SynchronousBundleListener { + + /** The system bundle context */ + private final BundleContext context; + private ServiceRegistration<?> providerReg; + private ServiceRegistration<?> converterReg; + + /** Strings used to format other strings */ + private final static String tab = "\t"; //$NON-NLS-1$ + private final static String newline = "\r\n"; //$NON-NLS-1$ + + /** this list contains the bundles known to be lazily awaiting activation */ + private final List<Bundle> lazyActivation = new ArrayList<Bundle>(); + + private Activator activator; + + /** commands provided by this command provider */ + private static final String[] functions = new String[] {"exit", "shutdown", "sta", "start", "sto", "stop", "i", + "install", "up", "up", "up", "update", "update", "update", "un", "uninstall", "s", "status", "se", "services", + "p", "p", "packages", "packages", "bundles", "b", "bundle", "gc", "init", "close", "r", "refresh", "exec", + "fork", "h", "headers", "pr", "props", "setp", "setprop", "ss", "t", "threads", "sl", "setfwsl", "setbsl", + "setibsl", "requiredBundles", "classSpaces", "profilelog", "getPackages", "getprop"}; + + /** + * Constructor. + * + * start() must be called after creating this object. + * + * @param framework The current instance of the framework + */ + public EquinoxCommandProvider(BundleContext context, Activator activator) { + this.context = context; + this.activator = activator; + } + + /** + * Starts this CommandProvider. + * + * Registers this object as a service providing commands + * Adds this object as a SynchronousBundleListener. + */ + public void start() { + EquinoxCommandsConverter converter = new EquinoxCommandsConverter(context); + converterReg = context.registerService(Converter.class.getName(), converter, null); + + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE)); + props.put(CommandProcessor.COMMAND_SCOPE, "equinox"); + props.put(CommandProcessor.COMMAND_FUNCTION, functions); + providerReg = context.registerService(EquinoxCommandProvider.class.getName(), this, props); + context.addBundleListener(this); + } + + public void stop() { + if (converterReg != null) { + converterReg.unregister(); + } + + context.removeBundleListener(this); + + if (providerReg != null) { + providerReg.unregister(); + } + } + + /** + * Handle the exit command. Exit immediately (System.exit) + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_EXIT_COMMAND_DESCRIPTION) + public void exit() throws Exception { + System.out.println(); + System.exit(0); + } + + /** + * Handle the shutdown command. Shutdown the OSGi framework. + * + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_SHUTDOWN_COMMAND_DESCRIPTION) + public void shutdown() throws Exception { + context.getBundle(0).stop(); + } + + /** + * Handle the start command's abbreviation. Invoke start() + * + * @param bundles bundle(s) to be started + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_START_COMMAND_DESCRIPTION) + public void sta(@Descriptor(ConsoleMsg.CONSOLE_HELP_START_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + start(bundles); + } + + /** + * Handle the start command. Start the specified bundle(s). + * + * @param bundles bundle(s) to be started + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_START_COMMAND_DESCRIPTION) + public void start(@Descriptor(ConsoleMsg.CONSOLE_HELP_START_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + if (bundles == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + if (bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + for(Bundle bundle : bundles) { + bundle.start(); + } + } + + /** + * Handle the stop command's abbreviation. Invoke stop() + * + * @param bundles bundle(s) to be stopped. + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_STOP_COMMAND_DESCRIPTION) + public void sto(@Descriptor(ConsoleMsg.CONSOLE_HELP_STOP_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + stop(bundles); + } + + /** + * Handle the stop command. Stop the specified bundle(s). + * + * @param bundles bundle(s) to be stopped. + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_STOP_COMMAND_DESCRIPTION) + public void stop(@Descriptor(ConsoleMsg.CONSOLE_HELP_STOP_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + if (bundles == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + if (bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + for(Bundle bundle : bundles) { + bundle.stop(); + } + } + + /** + * Handle the install command's abbreviation. Invoke install() + * + * @param shouldStart if the bundle should be start after installation + * @param url location of the bundle to be installed + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_COMMAND_DESCRIPTION) + public void i( + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_START_OPTION_DESCRIPTION) + @Parameter(absentValue = "false", presentValue = "true", names = { "-start" }) + boolean shouldStart, + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_START_ARGUMENT_DESCRIPTION) String url) throws Exception { + install(shouldStart, url); + } + + /** + * Handle the install command. Install and optionally start bundle from the given URL + * + * @param shouldStart if the bundle should be start after installation + * @param url location of the bundle to be installed + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_COMMAND_DESCRIPTION) + public Bundle install( + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_START_OPTION_DESCRIPTION) + @Parameter(absentValue = "false", presentValue = "true", names = { "-start" }) + boolean shouldStart, + @Descriptor(ConsoleMsg.CONSOLE_HELP_INSTALL_START_ARGUMENT_DESCRIPTION) String url) throws Exception { + if (url == null) { + System.out.println(ConsoleMsg.CONSOLE_NOTHING_TO_INSTALL_ERROR); + return null; + } else { + Bundle bundle = context.installBundle(url); + System.out.print(ConsoleMsg.CONSOLE_BUNDLE_ID_MESSAGE); + System.out.println(bundle.getBundleId()); + if (shouldStart == true) { + bundle.start(); + } + return bundle; + } + } + + /** + * Handle the update command's abbreviation. Invoke update() + * + * @param bundles bundle(s) to be updated + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_COMMAND_DESCRIPTION) + public void up(@Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + update(bundles); + } + + /** + * Handle the update command's abbreviation. Invoke update() + * + * @param bundle bundle to be updated + * @param source location to get the new bundle's content + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_DESCRIPTION) + public void up( + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_BUNDLE_ARGUMENT_DESCRIPTION) + Bundle bundle, + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_URL_ARGUMENT_DESCRIPTION) + URL source) throws Exception { + update(bundle, source); + } + + /** + * Handle the update command. Update the specified bundle(s). + * + * @param bundles bundle(s) to be updated + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_COMMAND_DESCRIPTION) + public void update(@Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + if (bundles == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + if(bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + for(Bundle bundle : bundles) { + bundle.update(); + } + } + + /** + * Handle the update command. Update the specified bundle with the specified content. + * + * @param bundle bundle to be updated + * @param source location to get the new bundle's content + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_DESCRIPTION) + public void update( + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_BUNDLE_ARGUMENT_DESCRIPTION) + Bundle bundle, + @Descriptor(ConsoleMsg.CONSOLE_HELP_UPDATE_SOURCE_COMMAND_URL_ARGUMENT_DESCRIPTION) + URL source) throws Exception { + bundle.update(source.openStream()); + } + + /** + * Handle the uninstall command's abbreviation. Invoke uninstall() + * + * @param bundles bundle(s) to uninstall + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UNINSTALL_COMMAND_DESCRIPTION) + public void un(@Descriptor(ConsoleMsg.CONSOLE_HELP_UNINSTALL_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + uninstall(bundles); + } + + /** + * Handle the uninstall command. Uninstall the specified bundle(s). + * + * @param bundles bundle(s) to uninstall + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_UNINSTALL_COMMAND_DESCRIPTION) + public void uninstall(@Descriptor(ConsoleMsg.CONSOLE_HELP_UNINSTALL_COMMAND_ARGUMENT_DESCRIPTION) Bundle[] bundles) throws Exception { + if(bundles == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + if(bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + for (Bundle bundle : bundles) { + bundle.uninstall(); + } + } + + private int getStatesFromConstants(String states) throws IllegalArgumentException{ + int stateFilter = -1; + if(!states.equals("")) { + StringTokenizer tokens = new StringTokenizer(states, ","); //$NON-NLS-1 + while (tokens.hasMoreElements()) { + String desiredState = (String) tokens.nextElement(); + Field match = null; + try { + match = Bundle.class.getField(desiredState.toUpperCase()); + if (stateFilter == -1) + stateFilter = 0; + stateFilter |= match.getInt(match); + } catch (NoSuchFieldException e) { + System.out.println(ConsoleMsg.CONSOLE_INVALID_INPUT + ": " + desiredState); //$NON-NLS-1$ + throw new IllegalArgumentException(); + } catch (IllegalAccessException e) { + System.out.println(ConsoleMsg.CONSOLE_INVALID_INPUT + ": " + desiredState); //$NON-NLS-1$ + throw new IllegalArgumentException(); + } + } + } + return stateFilter; + } + + /** + * Handle the status command's abbreviation. Invoke status() + * + * @param arguments + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_COMMAND_DESCRIPTION) + public void s(@Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_ARGUMENT_DESCRIPTION) String... arguments) throws Exception { + status(arguments); + } + + /** + * Handle the status command. Display installed bundles and registered services. + * + * @param arguments + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_COMMAND_DESCRIPTION) + public void status(@Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_ARGUMENT_DESCRIPTION) String... arguments) throws Exception { + if (context.getBundle(0).getState() == Bundle.ACTIVE) { + System.out.println(ConsoleMsg.CONSOLE_FRAMEWORK_IS_LAUNCHED_MESSAGE); + } else { + System.out.println(ConsoleMsg.CONSOLE_FRAMEWORK_IS_SHUTDOWN_MESSAGE); + } + System.out.println(); + + String states = ""; + String[] bsnSegments = null; + + if(arguments != null && arguments.length > 0) { + if(arguments[0].equals("-s")) { + if (arguments.length > 1) { + states = arguments[1]; + if(arguments.length > 2) { + bsnSegments = new String[arguments.length - 2]; + System.arraycopy(arguments, 2, bsnSegments, 0, bsnSegments.length); + } + } + } else { + bsnSegments = arguments; + } + } + + int stateFilter; + + try { + stateFilter = getStatesFromConstants(states); + } catch (IllegalArgumentException e) { + return; + } + + Bundle[] bundles = context.getBundles(); + int size = bundles.length; + + if (size == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_INSTALLED_BUNDLES_ERROR); + return; + } + + System.out.print(ConsoleMsg.CONSOLE_ID); + System.out.print(tab); + System.out.println(ConsoleMsg.CONSOLE_BUNDLE_LOCATION_MESSAGE); + System.out.println(ConsoleMsg.CONSOLE_STATE_BUNDLE_FILE_NAME_HEADER); + for (int i = 0; i < size; i++) { + Bundle bundle = bundles[i]; + if (!match(bundle, bsnSegments, stateFilter)) + continue; + System.out.print(bundle.getBundleId()); + System.out.print(tab); + System.out.println(bundle.getLocation()); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(getStateName(bundle)); + System.out.println(bundle.toString()); + } + + ServiceReference<?>[] services = context.getServiceReferences((String) null, (String) null); + if (services != null) { + System.out.println(ConsoleMsg.CONSOLE_REGISTERED_SERVICES_MESSAGE); + for(ServiceReference<?> service : services) { + System.out.println(service); + } + } + } + + /** + * Handle the services command's abbreviation. Invoke services() + * + * @param filters filters for services + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_SERVICES_COMMAND_DESCRIPTION) + public void se(@Descriptor(ConsoleMsg.CONSOLE_HELP_FILTER_ARGUMENT_DESCRIPTION)String... filters) throws Exception { + services(filters); + } + + /** + * Handle the services command. Display registered service details. + * + * @param filters filters for services + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_SERVICES_COMMAND_DESCRIPTION) + public void services(@Descriptor(ConsoleMsg.CONSOLE_HELP_FILTER_ARGUMENT_DESCRIPTION)String... filters) throws Exception { + String filter = null; + if (filters != null && filters.length > 0) { + StringBuffer buf = new StringBuffer(); + for (String singleFilter : filters) { + buf.append(' '); + buf.append(singleFilter); + } + filter = buf.toString(); + } + + InvalidSyntaxException originalException = null; + ServiceReference<?>[] services = null; + + try { + services = context.getServiceReferences((String) null, filter); + } catch (InvalidSyntaxException e) { + originalException = e; + } + + if (filter != null) { + filter = filter.trim(); + } + // If the filter is invalid and does not start with a bracket, probably the argument was the name of an interface. + // Try to construct an object class filter with this argument, and if still invalid - throw the original InvalidSyntaxException + if (originalException != null && !filter.startsWith("(") && !filter.contains(" ")) { //$NON-NLS-1$ //$NON-NLS-2$ + try { + filter = "(" + Constants.OBJECTCLASS + "=" + filter + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + services = context.getServiceReferences((String) null, filter); + } catch (InvalidSyntaxException e) { + throw originalException; + } + } else if (originalException != null) { + throw originalException; + } + + if (services != null) { + int size = services.length; + if (size > 0) { + for (int j = 0; j < size; j++) { + ServiceReference<?> service = services[j]; + System.out.println(service); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_REGISTERED_BY_BUNDLE_MESSAGE); + System.out.print(" "); //$NON-NLS-1$ + System.out.println(service.getBundle()); + Bundle[] users = service.getUsingBundles(); + if (users != null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_BUNDLES_USING_SERVICE_MESSAGE); + for (int k = 0; k < users.length; k++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(users[k]); + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLES_USING_SERVICE_MESSAGE); + } + } + return; + } + } + System.out.println(ConsoleMsg.CONSOLE_NO_REGISTERED_SERVICES_MESSAGE); + } + + /** + * Handle the packages command's abbreviation. Invoke packages() + * + * @param bundle bundle for which to display package details + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_COMMAND_DESCRIPTION) + public void p(@Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_BUNDLE_ARGUMENT_DESCRIPTION)Bundle... bundle) throws Exception { + packages(bundle); + } + + /** + * Handle the packages command's abbreviation. Invoke packages() + * + * @param packageName package for which to display details + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_COMMAND_DESCRIPTION) + public void p(@Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_PACKAGE_ARGUMENT_DESCRIPTION)String packageName) throws Exception { + packages(packageName); + } + + /** + * Handle the packages command. Display imported/exported packages details. + * + * @param bundle bundle for which to display package details + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_COMMAND_DESCRIPTION) + public void packages(@Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_BUNDLE_ARGUMENT_DESCRIPTION)Bundle... bundle) throws Exception { + if(activator.getPackageAdmin() != null) { + ExportedPackage[] exportedPackages; + if(bundle != null && bundle.length > 0) { + exportedPackages = activator.getPackageAdmin().getExportedPackages(bundle[0]); + } else { + exportedPackages = activator.getPackageAdmin().getExportedPackages((Bundle) null); + } + getPackages(exportedPackages); + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE); + } + } + + /** + * Handle the packages command. Display imported/exported packages details. + * + * @param packageName package for which to display details + **/ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_COMMAND_DESCRIPTION) + public void packages(@Descriptor(ConsoleMsg.CONSOLE_HELP_PACKAGES_PACKAGE_ARGUMENT_DESCRIPTION)String packageName) throws Exception { + if(activator.getPackageAdmin() != null) { + ExportedPackage[] exportedPackages = activator.getPackageAdmin().getExportedPackages(packageName); + getPackages(exportedPackages); + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE); + } + } + + @SuppressWarnings("deprecation") + private void getPackages(ExportedPackage[] packages) throws Exception { + if (packages == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_MESSAGE); + return; + } + for (int i = 0; i < packages.length; i++) { + org.osgi.service.packageadmin.ExportedPackage pkg = packages[i]; + System.out.print(pkg); + + boolean removalPending = pkg.isRemovalPending(); + if (removalPending) { + System.out.print("("); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_REMOVAL_PENDING_MESSAGE); + System.out.println(")"); //$NON-NLS-1$ + } + + org.osgi.framework.Bundle exporter = pkg.getExportingBundle(); + if (exporter != null) { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(exporter); + System.out.println(">"); //$NON-NLS-1$ + + org.osgi.framework.Bundle[] importers = pkg.getImportingBundles(); + for (int j = 0; j < importers.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.print(importers[j]); + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_IMPORTS_MESSAGE); + } + } else { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_STALE_MESSAGE); + System.out.println(">"); //$NON-NLS-1$ + } + } + } + + /** + * Handle the bundles command. Display details for all installed bundles. + * + * @param arguments + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_BUNDLES_COMMAND_DESCRIPTION) + public void bundles(@Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_ARGUMENT_DESCRIPTION) String... arguments) throws Exception { + String states = ""; + String[] bsnSegments = null; + + if(arguments != null && arguments.length > 0) { + if(arguments[0].equals("-s")) { + if (arguments.length > 1) { + states = arguments[1]; + if(arguments.length > 2) { + bsnSegments = new String[arguments.length - 2]; + System.arraycopy(arguments, 2, bsnSegments, 0, bsnSegments.length); + } + } + } else { + bsnSegments = arguments; + } + } + int stateFilter; + try { + stateFilter = getStatesFromConstants(states); + } catch (IllegalArgumentException e) { + return; + } + + Bundle[] bundles = context.getBundles(); + int size = bundles.length; + + if (size == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_INSTALLED_BUNDLES_ERROR); + return; + } + + for (int i = 0; i < size; i++) { + Bundle bundle = bundles[i]; + if (!match(bundle, bsnSegments, stateFilter)) + continue; + long id = bundle.getBundleId(); + System.out.println(bundle); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_ID_MESSAGE, String.valueOf(id))); + System.out.print(", "); //$NON-NLS-1$ + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_STATUS_MESSAGE, getStateName(bundle))); + if (id != 0) { + File dataRoot = bundle.getDataFile(""); //$NON-NLS-1$ + String root = (dataRoot == null) ? null : dataRoot.getAbsolutePath(); + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_DATA_ROOT_MESSAGE, root)); + } else { + System.out.println(); + } + + ServiceReference<?>[] services = bundle.getRegisteredServices(); + if (services != null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_REGISTERED_SERVICES_MESSAGE); + for (int j = 0; j < services.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(services[j]); + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_REGISTERED_SERVICES_MESSAGE); + } + + services = bundle.getServicesInUse(); + if (services != null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_SERVICES_IN_USE_MESSAGE); + for (int j = 0; j < services.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(services[j]); + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_SERVICES_IN_USE_MESSAGE); + } + } + } + + /** + * Handle the bundle command's abbreviation. Invoke bundle() + * + * @param bundles bundle(s) to display details for + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_BUNDLE_COMMAND_DESCRIPTION) + public void b(@Descriptor(ConsoleMsg.CONSOLE_HELP_IDLOCATION_ARGUMENT_DESCRIPTION)Bundle[] bundles) throws Exception { + bundle(bundles); + } + + /** + * Handle the bundle command. Display details for the specified bundle(s). + * + * @param bundles bundle(s) to display details for + */ + @SuppressWarnings({ "deprecation" }) + @Descriptor(ConsoleMsg.CONSOLE_HELP_BUNDLE_COMMAND_DESCRIPTION) + public void bundle(@Descriptor(ConsoleMsg.CONSOLE_HELP_IDLOCATION_ARGUMENT_DESCRIPTION)Bundle[] bundles) throws Exception { + if (bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return; + } + + for (Bundle bundle : bundles) { + long id = bundle.getBundleId(); + System.out.println(bundle); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_ID_MESSAGE, String.valueOf(id))); + System.out.print(", "); //$NON-NLS-1$ + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_STATUS_MESSAGE, getStateName(bundle))); + if (id != 0) { + File dataRoot = bundle.getDataFile(""); //$NON-NLS-1$ + String root = (dataRoot == null) ? null : dataRoot.getAbsolutePath(); + System.out.print(NLS.bind(ConsoleMsg.CONSOLE_DATA_ROOT_MESSAGE, root)); + System.out.println(); + } else { + System.out.println(); + } + + ServiceReference<?>[] services = bundle.getRegisteredServices(); + if (services != null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_REGISTERED_SERVICES_MESSAGE); + for (int j = 0; j < services.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(services[j]); + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_REGISTERED_SERVICES_MESSAGE); + } + + services = bundle.getServicesInUse(); + if (services != null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_SERVICES_IN_USE_MESSAGE); + for (int j = 0; j < services.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(services[j]); + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_SERVICES_IN_USE_MESSAGE); + } + + PackageAdmin packageAdmin = activator.getPackageAdmin(); + if (packageAdmin == null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE); + continue; + } + + PlatformAdmin platAdmin = activator.getPlatformAdmin(); + + if (platAdmin != null) { + BundleDescription desc = platAdmin.getState(false).getBundle(bundle.getBundleId()); + if (desc != null) { + boolean title = true; + + ExportPackageDescription[] exports = desc.getExportPackages(); + if (exports == null || exports.length == 0) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_MESSAGE); + } else { + title = true; + + for (int i = 0; i < exports.length; i++) { + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_EXPORTED_PACKAGES_MESSAGE); + title = false; + } + System.out.print(" "); //$NON-NLS-1$ + System.out.print(exports[i].getName()); + System.out.print("; version=\""); //$NON-NLS-1$ + System.out.print(exports[i].getVersion()); + System.out.print("\""); //$NON-NLS-1$ + if (desc.isRemovalPending()) { + System.out.println(ConsoleMsg.CONSOLE_EXPORTED_REMOVAL_PENDING_MESSAGE); + } else { + System.out.println(ConsoleMsg.CONSOLE_EXPORTED_MESSAGE); + } + } + + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_MESSAGE); + } + } + title = true; + if (desc != null) { + List<ImportPackageSpecification> fragmentsImportPackages = new ArrayList<ImportPackageSpecification>(); + + // Get bundle' fragments imports + BundleDescription[] fragments = desc.getFragments(); + for (int i = 0; i < fragments.length; i++) { + ImportPackageSpecification[] fragmentImports = fragments[i].getImportPackages(); + for (int j = 0; j < fragmentImports.length; j++) { + fragmentsImportPackages.add(fragmentImports[j]); + } + } + + // Get all bundle imports + ImportPackageSpecification[] importPackages; + if (fragmentsImportPackages.size() > 0) { + ImportPackageSpecification[] directImportPackages = desc.getImportPackages(); + importPackages = new ImportPackageSpecification[directImportPackages.length + fragmentsImportPackages.size()]; + + for (int i = 0; i < directImportPackages.length; i++) { + importPackages[i] = directImportPackages[i]; + } + + int offset = directImportPackages.length; + for (int i = 0; i < fragmentsImportPackages.size(); i++) { + importPackages[offset + i] = fragmentsImportPackages.get(i); + } + } else { + importPackages = desc.getImportPackages(); + } + + // Get all resolved imports + ExportPackageDescription[] imports = null; + imports = desc.getContainingState().getStateHelper().getVisiblePackages(desc, StateHelper.VISIBLE_INCLUDE_EE_PACKAGES | StateHelper.VISIBLE_INCLUDE_ALL_HOST_WIRES); + + // Get the unresolved optional and dynamic imports + List<ImportPackageSpecification> unresolvedImports = new ArrayList<ImportPackageSpecification>(); + + for (int i = 0; i < importPackages.length; i++) { + if (importPackages[i].getDirective(Constants.RESOLUTION_DIRECTIVE).equals(ImportPackageSpecification.RESOLUTION_OPTIONAL)) { + if (importPackages[i].getSupplier() == null) { + unresolvedImports.add(importPackages[i]); + } + } else if (importPackages[i].getDirective(org.osgi.framework.Constants.RESOLUTION_DIRECTIVE).equals(ImportPackageSpecification.RESOLUTION_DYNAMIC)) { + boolean isResolvable = false; + + // Check if the dynamic import can be resolved by any of the wired imports, + // and if not - add it to the list of unresolved imports + for (int j = 0; j < imports.length; j++) { + if (importPackages[i].isSatisfiedBy(imports[j])) { + isResolvable = true; + } + } + + if (isResolvable == false) { + unresolvedImports.add(importPackages[i]); + } + } + } + + title = printImportedPackages(imports, title); + + if (desc.isResolved() && (unresolvedImports.isEmpty() == false)) { + printUnwiredDynamicImports(unresolvedImports); + title = false; + } + } + + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_IMPORTED_PACKAGES_MESSAGE); + } + + if (packageAdmin != null) { + System.out.print(" "); //$NON-NLS-1$ + if ((packageAdmin.getBundleType(bundle) & PackageAdmin.BUNDLE_TYPE_FRAGMENT) > 0) { + org.osgi.framework.Bundle[] hosts = packageAdmin.getHosts(bundle); + if (hosts != null) { + System.out.println(ConsoleMsg.CONSOLE_HOST_MESSAGE); + for (int i = 0; i < hosts.length; i++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(hosts[i]); + } + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_HOST_MESSAGE); + } + } else { + org.osgi.framework.Bundle[] fragments = packageAdmin.getFragments(bundle); + if (fragments != null) { + System.out.println(ConsoleMsg.CONSOLE_FRAGMENT_MESSAGE); + for (int i = 0; i < fragments.length; i++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(fragments[i]); + } + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_FRAGMENT_MESSAGE); + } + } + + RequiredBundle[] requiredBundles = packageAdmin.getRequiredBundles(null); + RequiredBundle requiredBundle = null; + if (requiredBundles != null) { + for (RequiredBundle rb : requiredBundles) { + if (rb.getBundle() == bundle) { + requiredBundle = rb; + break; + } + } + } + + if (requiredBundle == null) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_NAMED_CLASS_SPACES_MESSAGE); + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NAMED_CLASS_SPACE_MESSAGE); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(requiredBundle); + if (requiredBundle.isRemovalPending()) { + System.out.println(ConsoleMsg.CONSOLE_REMOVAL_PENDING_MESSAGE); + } else { + System.out.println(ConsoleMsg.CONSOLE_PROVIDED_MESSAGE); + } + } + title = true; + if (requiredBundles != null) { + for (RequiredBundle rb : requiredBundles) { + if (rb == requiredBundle) + continue; + + org.osgi.framework.Bundle[] depBundles = rb.getRequiringBundles(); + if (depBundles == null) + continue; + + for (int j = 0; j < depBundles.length; j++) { + if (depBundles[j] == bundle) { + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out + .println(ConsoleMsg.CONSOLE_REQUIRED_BUNDLES_MESSAGE); + title = false; + } + System.out.print(" "); //$NON-NLS-1$ + System.out.print(rb); + + org.osgi.framework.Bundle provider = rb.getBundle(); + System.out.print("<"); //$NON-NLS-1$ + System.out.print(provider); + System.out.println(">"); //$NON-NLS-1$ + } + } + } + } + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_REQUIRED_BUNDLES_MESSAGE); + } + + } + } + System.out.println(); + System.out.println(); + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_NO_PLATFORM_ADMIN_MESSAGE); + } + } + } + + private boolean printImportedPackages(ExportPackageDescription[] importedPkgs, boolean title) { + for (int i = 0; i < importedPkgs.length; i++) { + if (title) { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_IMPORTED_PACKAGES_MESSAGE); + title = false; + } + System.out.print(" "); //$NON-NLS-1$ + System.out.print(importedPkgs[i].getName()); + System.out.print("; version=\""); //$NON-NLS-1$ + System.out.print(importedPkgs[i].getVersion()); + System.out.print("\""); //$NON-NLS-1$ + Bundle exporter = context.getBundle(importedPkgs[i].getSupplier().getBundleId()); + if (exporter != null) { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(exporter); + System.out.println(">"); //$NON-NLS-1$ + } else { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_STALE_MESSAGE); + System.out.println(">"); //$NON-NLS-1$ + } + } + return title; + } + + private void printUnwiredDynamicImports(List<ImportPackageSpecification> dynamicImports) { + for (int i = 0; i < dynamicImports.size(); i++) { + ImportPackageSpecification importPackage = dynamicImports.get(i); + System.out.print(" "); //$NON-NLS-1$ + System.out.print(importPackage.getName()); + System.out.print("; version=\""); //$NON-NLS-1$ + System.out.print(importPackage.getVersionRange()); + System.out.print("\""); //$NON-NLS-1$ + System.out.print("<"); //$NON-NLS-1$ + System.out.print("unwired"); //$NON-NLS-1$ + System.out.print(">"); //$NON-NLS-1$ + System.out.print("<"); //$NON-NLS-1$ + System.out.print(importPackage.getDirective(org.osgi.framework.Constants.RESOLUTION_DIRECTIVE)); + System.out.println(">"); //$NON-NLS-1$ + } + } + + /** + * Handle the gc command. Perform a garbage collection. + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_GC_COMMAND_DESCRIPTION) + public void gc() throws Exception { + long before = Runtime.getRuntime().freeMemory(); + + /* Let the finilizer finish its work and remove objects from its queue */ + System.gc(); /* asyncronous garbage collector might already run */ + System.gc(); /* to make sure it does a full gc call it twice */ + System.runFinalization(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // do nothing + } + + long after = Runtime.getRuntime().freeMemory(); + System.out.print(ConsoleMsg.CONSOLE_TOTAL_MEMORY_MESSAGE); + System.out.println(String.valueOf(Runtime.getRuntime().totalMemory())); + System.out.print(ConsoleMsg.CONSOLE_FREE_MEMORY_BEFORE_GARBAGE_COLLECTION_MESSAGE); + System.out.println(String.valueOf(before)); + System.out.print(ConsoleMsg.CONSOLE_FREE_MEMORY_AFTER_GARBAGE_COLLECTION_MESSAGE); + System.out.println(String.valueOf(after)); + System.out.print(ConsoleMsg.CONSOLE_MEMORY_GAINED_WITH_GARBAGE_COLLECTION_MESSAGE); + System.out.println(String.valueOf(after - before)); + } + + /** + * Handle the init command. Uninstall all bundles. + * + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_INIT_COMMAND_DESCRIPTION) + public void init() throws Exception { + if (context.getBundle(0).getState() == Bundle.ACTIVE) { + System.out.print(newline); + System.out.println(ConsoleMsg.CONSOLE_FRAMEWORK_LAUNCHED_PLEASE_SHUTDOWN_MESSAGE); + return; + } + + Bundle[] bundles = context.getBundles(); + + int size = bundles.length; + + if (size > 0) { + for (int i = 0; i < size; i++) { + Bundle bundle = bundles[i]; + + if (bundle.getBundleId() != 0) { + try { + bundle.uninstall(); + } catch (BundleException e) { + e.printStackTrace(); + } + } + } + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_INSTALLED_BUNDLES_ERROR); + } + PermissionAdmin securityAdmin = activator.getPermissionAdmin(); + ConditionalPermissionAdmin condPermAdmin = activator.getConditionalPermissionAdmin(); + if (securityAdmin != null) { + // clear the permissions from permission admin + securityAdmin.setDefaultPermissions(null); + String[] permLocations = securityAdmin.getLocations(); + if (permLocations != null) + for (int i = 0; i < permLocations.length; i++) + securityAdmin.setPermissions(permLocations[i], null); + ConditionalPermissionUpdate update = condPermAdmin.newConditionalPermissionUpdate(); + update.getConditionalPermissionInfos().clear(); + update.commit(); + } + // clear the permissions from conditional permission admin + if (securityAdmin != null) + for (Enumeration<ConditionalPermissionInfo> infos = condPermAdmin.getConditionalPermissionInfos(); infos.hasMoreElements();) + infos.nextElement().delete(); + } + + /** + * Handle the close command. Shutdown and exit. + + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_CLOSE_COMMAND_DESCRIPTION) + public void close() throws Exception { + context.getBundle(0).stop(); + System.exit(0); + } + + /** + * Handle the refresh command's abbreviation. Invoke refresh() + * + * @param bundles bundle(s) to be refreshed + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_REFRESH_COMMAND_DESCRIPTION) + public void r(@Descriptor(ConsoleMsg.CONSOLE_HELP_REFRESH_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundles) throws Exception { + refresh(bundles); + } + + /** + * Handle the refresh command. Refresh the packages of the specified bundles. + * + * @param bundles bundle(s) to be refreshed + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_REFRESH_COMMAND_DESCRIPTION) + public void refresh(@Descriptor(ConsoleMsg.CONSOLE_HELP_REFRESH_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundles) throws Exception { + PackageAdmin packageAdmin = activator.getPackageAdmin(); + if (packageAdmin != null) { + if(bundles != null && bundles.length > 0) { + packageAdmin.refreshPackages(bundles); + } else { + packageAdmin.refreshPackages(null); + } + } else { + System.out.println(ConsoleMsg.CONSOLE_CAN_NOT_REFRESH_NO_PACKAGE_ADMIN_ERROR); + } + } + + /** + * Executes the given system command in a separate system process + * and waits for it to finish. + * + * @param command command to be executed + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_EXEC_COMMAND_DESCRIPTION) + public void exec(@Descriptor(ConsoleMsg.CONSOLE_HELP_EXEC_COMMAND_ARGUMENT_DESCRIPTION) String command) throws Exception { + if (command == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_COMMAND_SPECIFIED_ERROR); + return; + } + + Process p = Runtime.getRuntime().exec(command); + + System.out.println(NLS.bind(ConsoleMsg.CONSOLE_STARTED_IN_MESSAGE, command, String.valueOf(p))); + int result = p.waitFor(); + System.out.println(NLS.bind(ConsoleMsg.CONSOLE_EXECUTED_RESULT_CODE_MESSAGE, command, String.valueOf(result))); + } + + /** + * Executes the given system command in a separate system process. It does + * not wait for a result. + * + * @param command command to be executed + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_FORK_COMMAND_DESCRIPTION) + public void fork(@Descriptor(ConsoleMsg.CONSOLE_HELP_FORK_COMMAND_ARGUMENT_DESCRIPTION) String command) throws Exception { + if (command == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_COMMAND_SPECIFIED_ERROR); + return; + } + + Process p = Runtime.getRuntime().exec(command); + System.out.println(NLS.bind(ConsoleMsg.CONSOLE_STARTED_IN_MESSAGE, command, String.valueOf(p))); + } + + /** + * Handle the headers command's abbreviation. Invoke headers() + * + * @param bundles bundle(s) whose headers to display + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_HEADERS_COMMAND_DESCRIPTION) + public List<Dictionary<String, String>> h(@Descriptor(ConsoleMsg.CONSOLE_HELP_HEADERS_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundles) throws Exception { + return headers(bundles); + } + + /** + * Handle the headers command. Display headers for the specified bundle(s). + * + * @param bundles bundle(s) whose headers to display + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_HEADERS_COMMAND_DESCRIPTION) + public List<Dictionary<String, String>> headers(@Descriptor(ConsoleMsg.CONSOLE_HELP_HEADERS_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundles) throws Exception { + ArrayList<Dictionary<String, String>> headers = new ArrayList<Dictionary<String,String>>(); + + if (bundles == null || bundles.length == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_BUNDLE_SPECIFIED_ERROR); + return headers; + } + + + for (Bundle bundle : bundles) { + headers.add(bundle.getHeaders()); + } + return headers; + } + + /** + * Handles the props command's abbreviation. Invokes props() + * + */ + @Descriptor(ConsoleMsg.CONSOLE_PROPS_COMMAND_DESCRIPTION) + public Dictionary<?, ?> pr() throws Exception { + return props(); + } + + /** + * Handles the _props command. Prints the system properties sorted. + * + */ + @Descriptor(ConsoleMsg.CONSOLE_PROPS_COMMAND_DESCRIPTION) + public Dictionary<?, ?> props() throws Exception { + System.out.println(ConsoleMsg.CONSOLE_SYSTEM_PROPERTIES_TITLE); + return System.getProperties(); + } + + /** + * Handles the setprop command's abbreviation. Invokes setprop() + * + * @param arguments key=value pairs for the new properties + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETPROP_COMMAND_DESCRIPTION) + public void setp(@Descriptor(ConsoleMsg.CONSOLE_HELP_SETPROP_COMMAND_ARGUMENTS_DESCRIPTION) String[] arguments) throws Exception { + setprop(arguments); + } + + /** + * Handles the setprop command. Sets the CDS property in the given argument. + * + * @param arguments key=value pairs for the new properties + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETPROP_COMMAND_DESCRIPTION) + public void setprop(@Descriptor(ConsoleMsg.CONSOLE_HELP_SETPROP_COMMAND_ARGUMENTS_DESCRIPTION) String[] arguments) throws Exception { + if (arguments == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_PARAMETERS_SPECIFIED_TITLE); + props(); + } else { + ServiceReference<EnvironmentInfo> envInfoRef = context.getServiceReference(EnvironmentInfo.class); + if (envInfoRef != null) { + // EnvironmentInfo is used because FrameworkProperties cannot be directly accessed outside of the system bundle + EnvironmentInfo envInfo = context.getService(envInfoRef); + if (envInfo != null) { + System.out.println(ConsoleMsg.CONSOLE_SETTING_PROPERTIES_TITLE); + for(String argument : arguments) { + int index = argument.indexOf("="); + if(index > -1) { + String key = argument.substring(0, index); + String value = argument.substring(index + 1, argument.length()); + envInfo.setProperty(key, value); + System.out.println(tab + key + " = " + value); //$NON-NLS-1$ + } + } + } else { + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_CANNOT_ACCESS_SYSTEM_PROPERTIES); + } + } + } + } + + /** + * Prints the short version of the status. + * For the long version use "status". + * + * @param arguments + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_SS_COMMAND_DESCRIPTION) + public void ss(@Descriptor(ConsoleMsg.CONSOLE_HELP_STATUS_ARGUMENT_DESCRIPTION) String... arguments) throws Exception { + if (context.getBundle(0).getState() == Bundle.ACTIVE) { + System.out.println(ConsoleMsg.CONSOLE_FRAMEWORK_IS_LAUNCHED_MESSAGE); + } else { + System.out.println(ConsoleMsg.CONSOLE_FRAMEWORK_IS_SHUTDOWN_MESSAGE); + } + System.out.println(); + + String states = ""; + String[] bsnSegments = null; + + if(arguments != null && arguments.length > 0) { + if(arguments[0].equals("-s")) { + if (arguments.length > 1) { + states = arguments[1]; + if(arguments.length > 2) { + bsnSegments = new String[arguments.length - 2]; + System.arraycopy(arguments, 2, bsnSegments, 0, bsnSegments.length); + } + } + } else { + bsnSegments = arguments; + } + } + + int stateFilter; + + try { + stateFilter = getStatesFromConstants(states); + } catch (IllegalArgumentException e) { + return; + } + + Bundle[] bundles = context.getBundles(); + int size = bundles.length; + + if (size == 0) { + System.out.println(ConsoleMsg.CONSOLE_NO_INSTALLED_BUNDLES_ERROR); + return; + } else { + System.out.print(newline); + System.out.print(ConsoleMsg.CONSOLE_ID); + System.out.print(tab); + System.out.println(ConsoleMsg.CONSOLE_STATE_BUNDLE_TITLE); + for (Bundle b : bundles) { + + if (!match(b, bsnSegments, stateFilter)) + continue; + String label = b.getSymbolicName(); + if (label == null || label.length() == 0) + label = b.toString(); + else + label = label + "_" + b.getVersion(); //$NON-NLS-1$ + System.out.println(b.getBundleId() + "\t" + getStateName(b) + label); //$NON-NLS-1$ + PackageAdmin packageAdmin = activator.getPackageAdmin(); + if ((packageAdmin.getBundleType(b) & PackageAdmin.BUNDLE_TYPE_FRAGMENT) != 0) { + Bundle[] hosts = packageAdmin.getHosts(b); + if (hosts != null) + for (int j = 0; j < hosts.length; j++) + System.out.println("\t Master=" + hosts[j].getBundleId()); //$NON-NLS-1$ + } else { + Bundle[] fragments = packageAdmin.getFragments(b); + if (fragments != null) { + System.out.print("\t Fragments="); //$NON-NLS-1$ + for (int f = 0; f < fragments.length; f++) { + Bundle fragment = fragments[f]; + System.out.print((f > 0 ? ", " : "") + fragment.getBundleId()); //$NON-NLS-1$ //$NON-NLS-2$ + } + System.out.println(); + } + } + } + } + } + + private boolean match(Bundle toFilter, String[] searchedName, int searchedState) { + if ((toFilter.getState() & searchedState) == 0) { + return false; + } + if (searchedName != null && searchedName.length > 0 && toFilter.getSymbolicName() != null && toFilter.getSymbolicName().indexOf(searchedName[0]) == -1) { + return false; + } + return true; + } + + /** + * Handles the threads command abbreviation. Invokes threads(). + * + */ + @Descriptor(ConsoleMsg.CONSOLE_THREADS_COMMAND_DESCRIPTION) + public void t() throws Exception { + threads(); + } + + /** + * Prints the information about the currently running threads + * in the embedded system. + * + */ + @Descriptor(ConsoleMsg.CONSOLE_THREADS_COMMAND_DESCRIPTION) + public void threads() throws Exception { + + ThreadGroup[] threadGroups = getThreadGroups(); + Util.sortByString(threadGroups); + + ThreadGroup tg = getTopThreadGroup(); + Thread[] threads = new Thread[tg.activeCount()]; + int count = tg.enumerate(threads, true); + Util.sortByString(threads); + + StringBuffer sb = new StringBuffer(120); + System.out.println(); + System.out.println(ConsoleMsg.CONSOLE_THREADGROUP_TITLE); + for (int i = 0; i < threadGroups.length; i++) { + tg = threadGroups[i]; + int all = tg.activeCount(); //tg.allThreadsCount(); + int local = tg.enumerate(new Thread[all], false); //tg.threadsCount(); + ThreadGroup p = tg.getParent(); + String parent = (p == null) ? "-none-" : p.getName(); //$NON-NLS-1$ + sb.setLength(0); + sb.append(Util.toString(simpleClassName(tg), 18)).append(" ").append(Util.toString(tg.getName(), 21)).append(" ").append(Util.toString(parent, 16)).append(Util.toString(Integer.valueOf(tg.getMaxPriority()), 3)).append(Util.toString(Integer.valueOf(local), 4)).append("/").append(Util.toString(String.valueOf(all), 6)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + System.out.println(sb.toString()); + } + System.out.print(newline); + System.out.println(ConsoleMsg.CONSOLE_THREADTYPE_TITLE); + for (int j = 0; j < count; j++) { + Thread t = threads[j]; + if (t != null) { + sb.setLength(0); + sb.append(Util.toString(simpleClassName(t), 18)).append(" ").append(Util.toString(t.getName(), 21)).append(" ").append(Util.toString(t.getThreadGroup().getName(), 16)).append(Util.toString(Integer.valueOf(t.getPriority()), 3)); //$NON-NLS-1$ //$NON-NLS-2$ + if (t.isDaemon()) + sb.append(" [daemon]"); //$NON-NLS-1$ + System.out.println(sb.toString()); + } + } + } + + /** + * Handles the sl (startlevel) command. + * + * @param bundle bundle to display startlevel for; if no bundle is specified, the framework startlevel is displayed + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_SL_COMMAND_DESCRIPTION) + public void sl(@Descriptor(ConsoleMsg.CONSOLE_HELP_SL_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundle) throws Exception { + StartLevel startLevel = activator.getStartLevel(); + if (startLevel != null) { + int value = 0; + if (bundle == null || bundle.length == 0) { // must want framework startlevel + value = startLevel.getStartLevel(); + System.out.println(NLS.bind(ConsoleMsg.STARTLEVEL_FRAMEWORK_ACTIVE_STARTLEVEL, String.valueOf(value))); + } else { // must want bundle startlevel + value = startLevel.getBundleStartLevel(bundle[0]); + System.out.println(NLS.bind(ConsoleMsg.STARTLEVEL_BUNDLE_STARTLEVEL, Long.valueOf(bundle[0].getBundleId()), Integer.valueOf(value))); + } + } + } + + /** + * Handles the setfwsl (set framework startlevel) command. + * + * @param newSL new value for the framewrok start level + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETFWSL_COMMAND_DESCRIPTION) + public void setfwsl(@Descriptor(ConsoleMsg.CONSOLE_HELP_SETFWSL_COMMAND_ARGUMENT_DESCRIPTION) int newSL) throws Exception { + StartLevel startLevel = activator.getStartLevel(); + if (startLevel != null) { + try { + startLevel.setStartLevel(newSL); + System.out.println(NLS.bind(ConsoleMsg.STARTLEVEL_FRAMEWORK_ACTIVE_STARTLEVEL, String.valueOf(newSL))); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + /** + * Handles the setbsl (set bundle startlevel) command. + * + * @param newSL new value for bundle start level + * @param bundles bundles whose start value will be changed + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETBSL_COMMAND_DESCRIPTION) + public void setbsl( + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETFWSL_COMMAND_ARGUMENT_DESCRIPTION)int newSL, + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETBSL_COMMAND_ARGUMENT_DESCRIPTION) Bundle... bundles) throws Exception { + StartLevel startLevel = activator.getStartLevel(); + if (startLevel != null) { + if (bundles == null) { + System.out.println(ConsoleMsg.STARTLEVEL_NO_STARTLEVEL_OR_BUNDLE_GIVEN); + return; + } + for (Bundle bundle : bundles) { + try { + startLevel.setBundleStartLevel(bundle, newSL); + System.out.println(NLS.bind(ConsoleMsg.STARTLEVEL_BUNDLE_STARTLEVEL, Long.valueOf(bundle.getBundleId()), Integer.valueOf(newSL))); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + } + + /** + * Handles the setibsl (set initial bundle startlevel) command. + * + * @param newInitialSL new value for initial start level + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_SETIBSL_COMMAND_DESCRIPTION) + public void setibsl(@Descriptor(ConsoleMsg.CONSOLE_HELP_SETFWSL_COMMAND_ARGUMENT_DESCRIPTION) int newInitialSL) throws Exception { + StartLevel startLevel = activator.getStartLevel(); + if (startLevel != null) { + try { + startLevel.setInitialBundleStartLevel(newInitialSL); + System.out.println(NLS.bind(ConsoleMsg.STARTLEVEL_INITIAL_BUNDLE_STARTLEVEL, String.valueOf(newInitialSL))); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + + /** + * Lists required bundles having the specified symbolic name or all if no bsn is specified + * + * @param symbolicName + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_DESCRIPTION) + public void requiredBundles(@Descriptor(ConsoleMsg.CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_ARGUMENT_DESCRIPTION) String... symbolicName) { + classSpaces(symbolicName); + } + + /** + * Lists required bundles having the specified symbolic name or all if no bsn is specified + * + * @param symbolicName + */ + @SuppressWarnings("deprecation") + @Descriptor(ConsoleMsg.CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_DESCRIPTION) + public void classSpaces(@Descriptor(ConsoleMsg.CONSOLE_HELP_REQUIRED_BUNDLES_COMMAND_ARGUMENT_DESCRIPTION) String... symbolicName) { + PackageAdmin packageAdmin = activator.getPackageAdmin(); + if (packageAdmin != null) { + RequiredBundle[] symBundles = null; + String name; + if(symbolicName == null || symbolicName.length == 0) { + name = null; + } else { + name = symbolicName[0]; + } + symBundles = packageAdmin.getRequiredBundles(name); + + if (symBundles == null) { + System.out.println(ConsoleMsg.CONSOLE_NO_NAMED_CLASS_SPACES_MESSAGE); + } else { + for (RequiredBundle symBundle : symBundles) { + + System.out.print(symBundle); + + boolean removalPending = symBundle.isRemovalPending(); + if (removalPending) { + System.out.print("("); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_REMOVAL_PENDING_MESSAGE); + System.out.println(")"); //$NON-NLS-1$ + } + + Bundle provider = symBundle.getBundle(); + if (provider != null) { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(provider); + System.out.println(">"); //$NON-NLS-1$ + + Bundle[] requiring = symBundle.getRequiringBundles(); + if (requiring != null) + for (int j = 0; j < requiring.length; j++) { + System.out.print(" "); //$NON-NLS-1$ + System.out.print(requiring[j]); + System.out.print(" "); //$NON-NLS-1$ + System.out.println(ConsoleMsg.CONSOLE_REQUIRES_MESSAGE); + } + } else { + System.out.print("<"); //$NON-NLS-1$ + System.out.print(ConsoleMsg.CONSOLE_STALE_MESSAGE); + System.out.println(">"); //$NON-NLS-1$ + } + + } + } + } else { + System.out.println(ConsoleMsg.CONSOLE_NO_EXPORTED_PACKAGES_NO_PACKAGE_ADMIN_MESSAGE); + } + } + + /** + * Handles the profilelog command. + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_PROFILELOG_COMMAND_DESCRIPTION) + public void profilelog() throws Exception { + Class<?> profileClass = BundleContext.class.getClassLoader().loadClass("org.eclipse.osgi.internal.profile.Profile"); + Method getProfileLog = profileClass.getMethod("getProfileLog", (Class<?>[]) null); + System.out.println(getProfileLog.invoke(null, (Object[]) null)); + } + + /** + * Lists all packages visible from the specified bundle + * @param bundle bundle to list visible packages + */ + @Descriptor(ConsoleMsg.CONSOLE_HELP_VISIBLE_PACKAGES_COMMAND_DESCRIPTION) + public void getPackages(@Descriptor(ConsoleMsg.CONSOLE_HELP_VISIBLE_PACKAGES_COMMAND_ARGUMENTS_DESCRIPTION) Bundle bundle) { + PlatformAdmin platformAdmin = activator.getPlatformAdmin(); + if (platformAdmin == null) + return; + BundleDescription bundleDescription = platformAdmin.getState(false).getBundle(bundle.getBundleId()); + ExportPackageDescription[] exports = platformAdmin.getStateHelper().getVisiblePackages(bundleDescription, StateHelper.VISIBLE_INCLUDE_EE_PACKAGES | StateHelper.VISIBLE_INCLUDE_ALL_HOST_WIRES); + for (int i = 0; i < exports.length; i++) { + System.out.println(exports[i] + ": " + platformAdmin.getStateHelper().getAccessCode(bundleDescription, exports[i])); //$NON-NLS-1$ + } + } + + /** + * Given a string containing a startlevel value, validate it and convert it to an int + * + * @param intp A CommandInterpreter object used for printing out error messages + * @param value A string containing a potential startlevel + * @return The start level or an int <0 if it was invalid + */ + protected int getStartLevelFromToken(String value) { + int retval = -1; + try { + retval = Integer.parseInt(value); + if (Integer.parseInt(value) <= 0) { + System.out.println(ConsoleMsg.STARTLEVEL_POSITIVE_INTEGER); + } + } catch (NumberFormatException nfe) { + System.out.println(ConsoleMsg.STARTLEVEL_POSITIVE_INTEGER); + } + return retval; + } + + /** + * Given a bundle, return the string describing that bundle's state. + * + * @param bundle A bundle to return the state of + * @return A String describing the state + */ + protected String getStateName(Bundle bundle) { + int state = bundle.getState(); + switch (state) { + case Bundle.UNINSTALLED : + return "UNINSTALLED "; //$NON-NLS-1$ + + case Bundle.INSTALLED : + if (isDisabled(bundle)) { + return "<DISABLED> "; //$NON-NLS-1$ + } + return "INSTALLED "; //$NON-NLS-1$ + + case Bundle.RESOLVED : + return "RESOLVED "; //$NON-NLS-1$ + + case Bundle.STARTING : + synchronized (lazyActivation) { + if (lazyActivation.contains(bundle)) { + return "<<LAZY>> "; //$NON-NLS-1$ + } + return "STARTING "; //$NON-NLS-1$ + } + + case Bundle.STOPPING : + return "STOPPING "; //$NON-NLS-1$ + + case Bundle.ACTIVE : + return "ACTIVE "; //$NON-NLS-1$ + + default : + return Integer.toHexString(state); + } + } + + private boolean isDisabled(Bundle bundle) { + boolean disabled = false; + ServiceReference<?> platformAdminRef = null; + try { + platformAdminRef = context.getServiceReference(PlatformAdmin.class.getName()); + if (platformAdminRef != null) { + PlatformAdmin platAdmin = (PlatformAdmin) context.getService(platformAdminRef); + if (platAdmin != null) { + State state = platAdmin.getState(false); + BundleDescription bundleDesc = state.getBundle(bundle.getBundleId()); + DisabledInfo[] disabledInfos = state.getDisabledInfos(bundleDesc); + if ((disabledInfos != null) && (disabledInfos.length != 0)) { + disabled = true; + } + } + } + } finally { + if (platformAdminRef != null) + context.ungetService(platformAdminRef); + } + return disabled; + } + + /** + * Answers all thread groups in the system. + * + * @return An array of all thread groups. + */ + protected ThreadGroup[] getThreadGroups() { + ThreadGroup tg = getTopThreadGroup(); + ThreadGroup[] groups = new ThreadGroup[tg.activeGroupCount()]; + int count = tg.enumerate(groups, true); + if (count == groups.length) { + return groups; + } + // get rid of null entries + ThreadGroup[] ngroups = new ThreadGroup[count]; + System.arraycopy(groups, 0, ngroups, 0, count); + return ngroups; + } + + /** + * Answers the top level group of the current thread. + * <p> + * It is the 'system' or 'main' thread group under + * which all 'user' thread groups are allocated. + * + * @return The parent of all user thread groups. + */ + protected ThreadGroup getTopThreadGroup() { + ThreadGroup topGroup = Thread.currentThread().getThreadGroup(); + if (topGroup != null) { + while (topGroup.getParent() != null) { + topGroup = topGroup.getParent(); + } + } + return topGroup; + } + + /** + * Returns the simple class name of an object. + * + * @param o The object for which a class name is requested + * @return The simple class name. + */ + public String simpleClassName(Object o) { + java.util.StringTokenizer t = new java.util.StringTokenizer(o.getClass().getName(), "."); //$NON-NLS-1$ + int ct = t.countTokens(); + for (int i = 1; i < ct; i++) { + t.nextToken(); + } + return t.nextToken(); + } + + @Descriptor(ConsoleMsg.CONSOLE_HELP_GETPROP_COMMAND_DESCRIPTION) + public void getprop(@Descriptor(ConsoleMsg.CONSOLE_HELP_GETPROP_COMMAND_ARGUMENT_DESCRIPTION) String... propName) throws Exception { + Properties allProperties = System.getProperties(); + Iterator<?> propertyNames = new TreeSet<Object>(allProperties.keySet()).iterator(); + while (propertyNames.hasNext()) { + String prop = (String) propertyNames.next(); + if (propName == null || propName.length == 0 || prop.startsWith(propName[0])) { + System.out.println(prop + '=' + allProperties.getProperty(prop)); + } + } + } + + /** + * This is used to track lazily activated bundles. + */ + public void bundleChanged(BundleEvent event) { + int type = event.getType(); + Bundle bundle = event.getBundle(); + synchronized (lazyActivation) { + switch (type) { + case BundleEvent.LAZY_ACTIVATION : + if (!lazyActivation.contains(bundle)) { + lazyActivation.add(bundle); + } + break; + + default : + lazyActivation.remove(bundle); + break; + } + } + + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandsConverter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandsConverter.java new file mode 100755 index 000000000..76ce91115 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/EquinoxCommandsConverter.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.commands; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.List; + +import org.apache.felix.service.command.Converter; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Version; + +/** + * Converter for the arguments of the migrated equinox commands. + */ +public class EquinoxCommandsConverter implements Converter { + BundleContext context; + + public EquinoxCommandsConverter(BundleContext context) { + this.context = context; + } + + public Object convert(Class<?> desiredType, Object in) throws Exception { + if(desiredType == Bundle[].class) { + if (in instanceof String) { + if("*".equals((String) in)) { + return context.getBundles(); + } + } else if (in instanceof List<?>) { + List<?> args = (List<?>) in; + if (checkStringElements(args)) { + try { + ArrayList<Bundle> bundles = new ArrayList<Bundle>(); + for (Object arg : args) { + long id = Long.parseLong((String)arg); + bundles.add(context.getBundle(id)); + } + return bundles.toArray(new Bundle[0]); + } catch (Exception e) { + return null; + } + } + } + } + + if(desiredType == Bundle.class) { + Bundle bundle = null; + try { + long id = Long.parseLong((String) in); + bundle = context.getBundle(id); + } catch (NumberFormatException nfe) { + + // if not found, assume token is either symbolic name@version, or location + String symbolicName = (String) in; + Version version = null; + + // check for @ -- this may separate either the version string, or be part of the + // location + int ix = symbolicName.indexOf("@"); //$NON-NLS-1$ + if (ix != -1) { + if ((ix + 1) != symbolicName.length()) { + try { + // if the version parses, then use the token prior to @ as a symbolic name + version = Version.parseVersion(symbolicName.substring(ix + 1, symbolicName.length())); + symbolicName = symbolicName.substring(0, ix); + } catch (IllegalArgumentException e) { + // version doesn't parse, assume token is symbolic name without version, or location + } + } + } + + Bundle[] bundles = context.getBundles(); + for (Bundle b : bundles) { + + // if symbolicName matches, then matches if there is no version specific on command, or the version matches + // if there is no version specified on command, pick first matching bundle + if ((symbolicName.equals(b.getSymbolicName()) && (version == null || version.equals(b.getVersion()))) || ((String)in).equals(b.getLocation())) { + bundle = b; + break; + } + } + } + return bundle; + } + + if (desiredType == URL.class) { + URL url = null; + try { + url = new URL((String) in); + } catch (Exception e) { + //do nothing + } + return url; + } + + return null; + } + + private boolean checkStringElements(List<?> list) { + for (Object element : list) { + if (!(element instanceof String)) { + return false; + } + } + + return true; + } + + public CharSequence format(Object target, int level, Converter escape) throws Exception { + if (target instanceof Dictionary<?, ?>) { + Dictionary<?, ?> dic = (Dictionary<?, ?>) target; + return printDictionary(dic); + } + + if (target instanceof List<?>) { + List<?> list = (List<?>) target; + if (checkDictionaryElements(list)) { + StringBuilder builder = new StringBuilder(); + for(Object dic : list) { + builder.append("Bundle headers:\r\n"); + builder.append(printDictionary((Dictionary<?, ?>)dic)); + builder.append("\r\n"); + builder.append("\r\n"); + } + return builder.toString(); + } + } + + return null; + } + + private boolean checkDictionaryElements(List<?> list) { + for (Object element : list) { + if (!(element instanceof Dictionary<?, ?>)) { + return false; + } + } + + return true; + } + + private String printDictionary(Dictionary<?, ?> dic) { + int count = dic.size(); + String[] keys = new String[count]; + Enumeration<?> keysEnum = dic.keys(); + int i = 0; + while (keysEnum.hasMoreElements()) { + keys[i++] = (String) keysEnum.nextElement(); + } + Util.sortByString(keys); + + StringBuilder builder = new StringBuilder(); + for (i = 0; i < count; i++) { + builder.append(" " + keys[i] + " = " + dic.get(keys[i])); //$NON-NLS-1$//$NON-NLS-2$ + builder.append("\r\n"); + } + builder.append("\r\n"); + return builder.toString(); + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/HelpCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/HelpCommand.java new file mode 100755 index 000000000..f034dc4a1 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/HelpCommand.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.commands; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.command.adapter.CustomCommandInterpreter; +import org.eclipse.osgi.framework.console.CommandInterpreter; +import org.eclipse.osgi.framework.console.CommandProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * This class provides help for the legacy equinox commands, which are adapted to Gogo commands. + */ +public class HelpCommand { + private BundleContext context; + private Set<CommandProvider> legacyCommandProviders; + private ServiceTracker<CommandProvider, Set<CommandProvider>> commandProvidersTracker; + + public class CommandProviderCustomizer implements ServiceTrackerCustomizer<CommandProvider, Set<CommandProvider>> { + private BundleContext context; + public CommandProviderCustomizer(BundleContext context) { + this.context = context; + } + + public Set<CommandProvider> addingService( + ServiceReference<CommandProvider> reference) { + if (reference.getProperty("osgi.command.function") != null) { + // must be a gogo function already; don' track + return null; + } + CommandProvider command = context.getService(reference); + legacyCommandProviders.add(command); + context.ungetService(reference); + return legacyCommandProviders; + } + + public void modifiedService( + ServiceReference<CommandProvider> reference, + Set<CommandProvider> service) { + // nothing to do + } + + public void removedService(ServiceReference<CommandProvider> reference, + Set<CommandProvider> providers) { + CommandProvider provider = context.getService(reference); + providers.remove(provider); + } + + } + + public HelpCommand(BundleContext context) { + this.context = context; + legacyCommandProviders = new HashSet<CommandProvider>(); + commandProvidersTracker = new ServiceTracker<CommandProvider, Set<CommandProvider>>(context, CommandProvider.class.getName(), new CommandProviderCustomizer(context)); + commandProvidersTracker.open(); + } + + public void start() { + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE)); + props.put(CommandProcessor.COMMAND_SCOPE, "equinox"); + props.put(CommandProcessor.COMMAND_FUNCTION, new String[] {"help"}); + context.registerService(HelpCommand.class.getName(), this, props); + } + + /** + * Provides help for the available commands. The Gogo help command, used with no arguments, prints the names + * of all registered commands. If a command name is passed as argument to the help command, then the help + * message for the particular command is displayed (if such is defined). + * + * This method can accept an additional argument -legacy. If this option is specified, the names of all + * legacy equinox commands are displayed. If -legacy is not specified, then only the Gogo help command is called. + * + * If -legacy is displayed along with a command name, then the legacy commands are searched + * for a command with this name, and the help message for this command is displayed, if provided. If the + * CommandProvider, which provides this command, does not provide help for individual commands, then + * the help for all commands in the CommandProvider is displayed. + * + * This method can accept an additional argument -all. If this option is specified, then both the names of the + * legacy equinox commands and the Gogo commands are displayed. + * + * @param session + * @param args + * @throws Exception + */ + public void help(final CommandSession session, String... args) throws Exception { + String command = null; + + if (args.length > 0) { + command = args[0]; + } + + if (command != null) { + for (CommandProvider provider : legacyCommandProviders) { + Method[] methods = provider.getClass().getMethods(); + for (Method method : methods) { + Object retval = null; + if (method.getName().equals("_" + command)) { + try { + Method helpMethod = provider.getClass().getMethod("_help", CommandInterpreter.class); + ArrayList<Object> argsList = new ArrayList<Object>(); + argsList.add(command); + retval = helpMethod.invoke(provider, new CustomCommandInterpreter(argsList)); + } catch (Exception e) { + System.out.println(provider.getHelp()); + break; + } + + if (retval != null && retval instanceof String) { + System.out.println(retval); + } + break; + } + } + } + + try { + session.execute("felix:help " + command); + } catch (IllegalArgumentException e) { + handleCommandNotFound(); + } + + return; + } + + printLegacyCommands(); + try { + session.execute("felix:help"); + } catch (IllegalArgumentException e) { + handleCommandNotFound(); + } + + } + + private void printLegacyCommands() { + for (CommandProvider provider : legacyCommandProviders) { + Method[] methods = provider.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("_") && !method.getName().equals("_help")) { + System.out.println("equinox:" + method.getName().substring(1)); + } + } + } + } + + private boolean checkStarted(String symbolicName) { + Bundle[] bundles = context.getBundles(); + for (Bundle bundle : bundles) { + if (bundle.getSymbolicName().equals(symbolicName) && bundle.getState() == Bundle.ACTIVE) { + return true; + } + } + + return false; + } + + private void handleCommandNotFound() { + if (checkStarted("org.apache.felix.gogo.command")) { + System.out.println("Cannot find felix:help command"); + } else { + System.out.println("Cannot find felix:help command; bundle org.apache.felix.gogo.command is not started"); + } + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ManCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ManCommand.java new file mode 100755 index 000000000..da5441f8c --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/ManCommand.java @@ -0,0 +1,43 @@ +package org.eclipse.equinox.console.commands; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.osgi.framework.BundleContext; + +public class ManCommand { + private BundleContext context; + + public ManCommand(BundleContext context) { + this.context = context; + } + + public void start() { + Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(CommandProcessor.COMMAND_SCOPE, "equinox"); + props.put(CommandProcessor.COMMAND_FUNCTION, new String[] {"man"}); + context.registerService(ManCommand.class.getName(), this, props); + } + + public void man(CommandSession session, String... args) throws Exception { + StringBuilder builder = null; + if (args.length > 0) { + builder = new StringBuilder(); + for(String arg : args) { + builder.append(arg); + builder.append(" "); + } + } + + String cmdForExecution = null; + if (builder != null) { + cmdForExecution = "equinox:help" + " " + builder.toString().trim(); + } else { + cmdForExecution = "equinox:help"; + } + + session.execute(cmdForExecution); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/Util.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/Util.java new file mode 100755 index 000000000..bb3c19200 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/commands/Util.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.commands; + +/** + * This class contains utility functions. + */ +public class Util { + /** + * Performs a quicksort of the given objects + * by their string representation in ascending order. + * <p> + * + * @param array The array of objects to sort + */ + public static void sortByString(Object[] array) { + qSortByString(array, 0, array.length - 1); + } + + /** + * Sorts the array of objects by their string representation + * in ascending order. + * <p> + * This is a version of C.A.R Hoare's Quick Sort algorithm. + * + * @param array the array of objects to sort + * @param start the start index to begin sorting + * @param stop the end index to stop sorting + * + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end >= array.length</code> + */ + public static void qSortByString(Object[] array, int start, int stop) { + if (start >= stop) + return; + + int left = start; // left index + int right = stop; // right index + Object temp; // for swapping + + // arbitrarily establish a partition element as the midpoint of the array + String mid = String.valueOf(array[(start + stop) / 2]); + + // loop through the array until indices cross + while (left <= right) { + // find the first element that is smaller than the partition element from the left + while ((left < stop) && (String.valueOf(array[left]).compareTo(mid) < 0)) { + ++left; + } + // find an element that is smaller than the partition element from the right + while ((right > start) && (mid.compareTo(String.valueOf(array[right])) < 0)) { + --right; + } + // if the indices have not crossed, swap + if (left <= right) { + temp = array[left]; + array[left] = array[right]; + array[right] = temp; + ++left; + --right; + } + } + // sort the left partition, if the right index has not reached the left side of array + if (start < right) { + qSortByString(array, start, right); + } + // sort the right partition, if the left index has not reached the right side of array + if (left < stop) { + qSortByString(array, left, stop); + } + } + + /** + * Sorts the specified range in the array in ascending order. + * + * @param array the Object array to be sorted + * @param start the start index to sort + * @param end the last + 1 index to sort + * + * @exception ClassCastException when an element in the array does not + * implement Comparable or elements cannot be compared to each other + * @exception IllegalArgumentException when <code>start > end</code> + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end > array.size()</code> + */ + @SuppressWarnings("unchecked") + public static void sort(Object[] array, int start, int end) { + int middle = (start + end) / 2; + if (start + 1 < middle) + sort(array, start, middle); + if (middle + 1 < end) + sort(array, middle, end); + if (start + 1 >= end) + return; // this case can only happen when this method is called by the user + if (((Comparable<Object>) array[middle - 1]).compareTo(array[middle]) <= 0) + return; + if (start + 2 == end) { + Object temp = array[start]; + array[start] = array[middle]; + array[middle] = temp; + return; + } + int i1 = start, i2 = middle, i3 = 0; + Object[] merge = new Object[end - start]; + while (i1 < middle && i2 < end) { + merge[i3++] = ((Comparable<Object>) array[i1]).compareTo(array[i2]) <= 0 ? array[i1++] : array[i2++]; + } + if (i1 < middle) + System.arraycopy(array, i1, merge, i3, middle - i1); + System.arraycopy(merge, 0, array, start, i2 - start); + } + + /** + * Sorts the specified range in the array in descending order. + * + * @param array the Object array to be sorted + * @param start the start index to sort + * @param end the last + 1 index to sort + * + * @exception ClassCastException when an element in the array does not + * implement Comparable or elements cannot be compared to each other + * @exception IllegalArgumentException when <code>start > end</code> + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end > array.size()</code> + */ + public static void dsort(Object[] array, int start, int end) { + // first sort in ascending order + sort(array, start, end); + // then swap the elements in the array + swap(array); + } + + /** + * Reverse the elements in the array. + * + * @param array the Object array to be reversed + */ + public static void swap(Object[] array) { + int start = 0; + int end = array.length - 1; + while (start < end) { + Object temp = array[start]; + array[start++] = array[end]; + array[end--] = temp; + } + } + + /** + * Returns a string representation of the object + * in the given length. + * If the string representation of the given object + * is longer then it is truncated. + * If it is shorter then it is padded with the blanks + * to the given total length. + * If the given object is a number then the padding + * is done on the left, otherwise on the right. + * + * @param object the object to convert + * @param length the length the output string + */ + public static String toString(Object object, int length) { + boolean onLeft = object instanceof Number; + return toString(object, length, ' ', onLeft); + } + + /** + * Returns a string representation of the object + * in the given length. + * If the string representation of the given object + * is longer then it is truncated. + * If it is shorter then it is padded to the left or right + * with the given character to the given total length. + * + * @param object the object to convert + * @param length the length the output string + * @param pad the pad character + * @param onLeft if <code>true</code> pad on the left, otherwise an the right + */ + public static String toString(Object object, int length, char pad, boolean onLeft) { + String input = String.valueOf(object); + int size = input.length(); + if (size >= length) { + int start = (onLeft) ? size - length : 0; + return input.substring(start, length); + } + + StringBuffer padding = new StringBuffer(length - size); + for (int i = size; i < length; i++) + padding.append(pad); + + StringBuffer stringBuffer = new StringBuffer(length); + if (onLeft) + stringBuffer.append(padding.toString()); + stringBuffer.append(input); + if (!onLeft) + stringBuffer.append(padding.toString()); + return stringBuffer.toString(); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleInputStream.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleInputStream.java new file mode 100755 index 000000000..73ab4ec32 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleInputStream.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ +package org.eclipse.equinox.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 int read(byte b[], int off, int len) throws IOException { + if (len == 0) { + return len; + } + + int currOff = off; + int readCnt = 0; + + if (current != null) { + int i; + while (pos > 0 && readCnt < len) { + i = read(); + if (i == -1) { + return (readCnt > 0) ? readCnt : i; + } + b[currOff] = (byte) i; + currOff++; + readCnt++; + } + } else { + int i = read(); + if (i == -1) { + return i; + } + b[currOff] = (byte) i; + currOff++; + readCnt++; + while (pos > 0 && readCnt < len) { + i = read(); + if (i == -1) { + return (readCnt > 0) ? readCnt : i; + } + b[currOff] = (byte) i; + currOff++; + readCnt++; + } + } + + return readCnt; + }*/ + + public synchronized int read(byte b[], int off, int len) throws IOException { + if (len == 0) { + return len; + } + + int currOff = off; + int readCnt = 0; + int i; + + if (current == null) { + i = read(); + if (i == -1) { + return i; + } + b[currOff] = (byte) i; + currOff++; + readCnt++; + } + + while ((pos > 0 || !buffer.isEmpty()) && readCnt < len) { + i = read(); + if (i == -1) { + return (readCnt > 0) ? readCnt : i; + } + b[currOff] = (byte) i; + currOff++; + readCnt++; + } + + return readCnt; + } + + 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleOutputStream.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleOutputStream.java new file mode 100755 index 000000000..575342c25 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/ConsoleOutputStream.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.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) { + try { + out.write(buffer, 0, pos); + out.flush(); + } finally { + 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/InputHandler.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/InputHandler.java new file mode 100755 index 000000000..38451ad75 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/InputHandler.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/KEYS.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/KEYS.java new file mode 100755 index 000000000..87f256da6 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/KEYS.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common; + +/** + * The supported escape sequences. + */ +public enum KEYS { + UP, + DOWN, + RIGHT, + LEFT, + CENTER, + HOME, + END, + PGUP, + PGDN, + INS, + DEL, + UNFINISHED, + UNKNOWN +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/Scanner.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/Scanner.java new file mode 100755 index 000000000..08e6f077e --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/Scanner.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.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 { + + private byte BACKSPACE; + private byte DEL; + protected static final byte BS = 8; + protected static final byte LF = 10; + protected static final byte CR = 13; + protected static final byte ESC = 27; + protected static final byte SPACE = 32; + protected static final byte MAX_CHAR = 127; + protected static final String DEFAULT_TTYPE = File.separatorChar == '/' ? "XTERM" : "ANSI"; + // shows if user input should be echoed to the console + private boolean isEchoEnabled = true; + + 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", vtMappings); + supportedEscapeSequences.put("XTERM", vtMappings); + supportedEscapeSequences.put("VT320", new VT320TerminalTypeMappings()); + supportedEscapeSequences.put("SCO", new SCOTerminalTypeMappings()); + } + + public abstract void scan(int b) throws IOException; + + public void toggleEchoEnabled(boolean isEnabled) { + isEchoEnabled = isEnabled; + } + + protected void echo(int b) throws IOException { + if (isEchoEnabled) { + 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) { + String[] copy = new String[escapes.length]; + System.arraycopy(escapes, 0, copy, 0, escapes.length); + return copy; + } else { + return null; + } + } + + public void setEscapes(String[] escapes) { + if (escapes != null) { + this.escapes = new String[escapes.length]; + System.arraycopy(escapes, 0, this.escapes, 0, escapes.length); + } else { + this.escapes = null; + } + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/SimpleByteBuffer.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/SimpleByteBuffer.java new file mode 100755 index 000000000..188b14bdb --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/SimpleByteBuffer.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/ANSITerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/ANSITerminalTypeMappings.java new file mode 100755 index 000000000..0396c27ec --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/ANSITerminalTypeMappings.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; + +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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/SCOTerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/SCOTerminalTypeMappings.java new file mode 100755 index 000000000..7bdef7d6c --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/SCOTerminalTypeMappings.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +import org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/TerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/TerminalTypeMappings.java new file mode 100755 index 000000000..53dc2a699 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/TerminalTypeMappings.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.equinox.console.common.KEYS; + +/** + * This is the base class for all supported terminal types. + * It contains the escape sequences, common for all mappings. + */ +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) { + String[] copy = new String[escapes.length]; + System.arraycopy(escapes, 0, copy, 0, escapes.length); + return copy; + } 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT100TerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT100TerminalTypeMappings.java new file mode 100755 index 000000000..19d36d431 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT100TerminalTypeMappings.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +import org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT220TerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT220TerminalTypeMappings.java new file mode 100755 index 000000000..9b832d9e8 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT220TerminalTypeMappings.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +/** + * For the supported escape sequences, the VT220 and XTERM sequences + * are one and the same. + * + */ +public class VT220TerminalTypeMappings extends ANSITerminalTypeMappings { + + public VT220TerminalTypeMappings() { + super(); + + BACKSPACE = 127; + DEL = -1; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT320TerminalTypeMappings.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT320TerminalTypeMappings.java new file mode 100755 index 000000000..5fcf382fc --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/common/terminal/VT320TerminalTypeMappings.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.common.terminal; + +import org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandLineParser.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandLineParser.java new file mode 100755 index 000000000..ecb18fb78 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandLineParser.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +/** + * This class determines the last token in the command line. Completion should be made for this token. + */ +public class CommandLineParser { + private static final char[] delimiters = new char[] {'|', ';', '=', '{', '}', '(', ')', '$', '>', ']', ' '}; + private static final String COMMENT_CHAR = "#"; + private static final String HASH_MAP_DEF_START_CHAR = "<"; + private static final String HASH_MAP_DEF_END_CHAR = ">"; + private static final String LIST_DEF_START_CHAR = "["; + /** + * Determine the last token in the command line. The last token is the substring, starting from + * one of the characters, considered as delimiters + * + * @param commandLine whole command line + * @param cursor current position in the command line + * @return the current token + */ + public static String getCurrentToken(String commandLine, int cursor) { + String current = commandLine.substring(0, cursor); + + int currentStartIdx = -1; + // determine the positioin of the last delimiter + for(char delimiter : delimiters) { + int idx = current.lastIndexOf(delimiter); + if (delimiter == '=' && idx > -1) { + // hash map is defined in a command with the syntax <key=value>; within this definition we do not want to + // make completion; determine if we are in such case + int startAngleBraceIdx = current.substring(0, idx).lastIndexOf(HASH_MAP_DEF_START_CHAR); + int endAngleBraceBeforeAssignmentIdx = current.substring(0, idx).lastIndexOf(HASH_MAP_DEF_END_CHAR); + int endAngleBraceAfterAssignmentIdx = current.substring(idx + 1).indexOf(HASH_MAP_DEF_END_CHAR); + if (startAngleBraceIdx > -1 && startAngleBraceIdx < idx && endAngleBraceBeforeAssignmentIdx == -1 && endAngleBraceAfterAssignmentIdx == -1) { + return null; + } + } + if (idx > currentStartIdx) { + currentStartIdx = idx; + } + } + + if (currentStartIdx + 1 == current.length()) { + return ""; + } + + if (currentStartIdx + 1 > current.length()) { + return null; + } + + String currentToken = current.substring(currentStartIdx + 1, current.length()); + + // if the current position is after the comment character, or within a hash map or list definition, do not do command completion + if (currentToken.contains(COMMENT_CHAR) || currentToken.contains(HASH_MAP_DEF_START_CHAR) || currentToken.contains(LIST_DEF_START_CHAR)) { + return null; + } + + return currentToken; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandNamesCompleter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandNamesCompleter.java new file mode 100755 index 000000000..5199a3360 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CommandNamesCompleter.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.completion.common.Completer; + +/** + * This class provides completion for command names. + * + */ +public class CommandNamesCompleter implements Completer { + + private CommandSession session; + private static final String COMMANDS = ".commands"; + + public CommandNamesCompleter(CommandSession session) { + this.session = session; + } + + public Map<String, Integer> getCandidates(String buffer, int cursor) { + // CommandSession.get(".commands") returns the names of all registered commands + @SuppressWarnings("unchecked") + Set<String> commandNames = (Set<String>) session.get(COMMANDS); + + // command names are stored in the session in lower case + String currentToken = CommandLineParser.getCurrentToken(buffer, cursor).toLowerCase(); + if(currentToken == null || currentToken.equals("")) { + return new HashMap<String, Integer>(); + } + + if (!currentToken.contains(":")) { + // the current token does not contain a scope qualifier, so remove scopes from possible candidates + commandNames = clearScopes(commandNames); + } + StringsCompleter completer = new StringsCompleter(commandNames, true); + return completer.getCandidates(buffer, cursor); + } + + private Set<String> clearScopes(Set<String> commandNames) { + Set<String> clearedCommandNames = new HashSet<String>(); + + for(String commandName : commandNames) { + clearedCommandNames.add(commandName.substring(commandName.indexOf(":") + 1)); + } + + return clearedCommandNames; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CompletionHandler.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CompletionHandler.java new file mode 100755 index 000000000..b805aeebe --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/CompletionHandler.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.completion.common.Completer; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** + * This class aggregates the different types of completers - variable, command and + * file completers. It also searches for registered custom completers and if available + * uses them too. It call all completers and finally returns the completion candidates + * returned from all of them. + * + */ +public class CompletionHandler { + + private BundleContext context; + private CommandSession session; + Set<Completer> completers; + private static final String FILE = "file"; + private static final char VARIABLE_PREFIX = '$'; + + public CompletionHandler(BundleContext context, CommandSession session) { + this.context = context; + this.session = session; + completers = new HashSet<Completer>(); + } + + public Map<String, Integer> getCandidates(byte[] buf, int cursor) { + String currentInput = new String(buf); + String currentToken = CommandLineParser.getCurrentToken(currentInput, cursor); + if (currentToken == null){ + return new HashMap<String, Integer>(); + } + if (currentToken.contains(FILE) == true) { + completers.add(new FileNamesCompleter()); + }else{ + if ((cursor - currentToken.length() > 0) && (buf[cursor - currentToken.length() - 1] == VARIABLE_PREFIX)){ + completers.add(new VariableNamesCompleter(session)); + }else { + completers.add(new CommandNamesCompleter(session)); + completers.add(new FileNamesCompleter()); + } + } + lookupCustomCompleters(); + Map<String, Integer> candidates = new TreeMap<String, Integer>(); + for (Completer completer : completers) { + candidates.putAll(completer.getCandidates(currentInput, cursor)); + } + + return candidates; + } + + @SuppressWarnings("unchecked") + private void lookupCustomCompleters (){ + ServiceReference<Completer>[] completersRefs = null; + try { + completersRefs = (ServiceReference<Completer>[]) context.getServiceReferences(Completer.class.getName(), null); + } catch (InvalidSyntaxException e) { + // do nothing + } + + if (completersRefs != null) { + for (ServiceReference<Completer> ref : completersRefs) { + Completer completer = context.getService(ref); + if (completer != null) { + completers.add(completer); + } + } + } + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/FileNamesCompleter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/FileNamesCompleter.java new file mode 100755 index 000000000..a69ab5e46 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/FileNamesCompleter.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.equinox.console.completion.common.Completer; + +/** + * This class implements completion of file names. It provides completion both for + * files with absolute filenames, as well as with names, relative to the current + * directory. + */ +public class FileNamesCompleter implements Completer { + private static final String FILE = "file:"; + + public Map<String, Integer> getCandidates(String buffer, int cursor) { + Map<String, Integer> result = new HashMap<String, Integer>(); + String currentToken = CommandLineParser.getCurrentToken(buffer, cursor); + if(currentToken == null || currentToken.equals("")) { + return new HashMap<String, Integer>(); + } + + // if current token contains file:, then use URL class to parse the filename + if(currentToken.contains(FILE)) { + String fileName = currentToken.substring(currentToken.indexOf(FILE)); + try { + URL url = new URL(fileName); + String canonicalFileName = url.getPath(); + File file = new File(canonicalFileName); + File parent = file.getParentFile(); + + if ((file.isDirectory() && canonicalFileName.endsWith("/") )|| parent == null) { + // the entered filename is a directory name, ending with file separator character - here + // all files in the directory will be returned as completion candidates; + // or, if parent is null, the file is in the root directory and + // the names of all files in this directory should be used to search for completion candidates + return checkChildren(file, "", cursor, false); + } else { + // there is a filename for completion, and the names of all files in the same directory will be used + // to search for completion candidates + return checkChildren(parent, file.getName(), cursor, false); + } + } catch (MalformedURLException e) { + return result; + } + } + + // the file name for completion is only the file separator character, so all files + // in the current directory will be returned as completion candidates + if (currentToken.equals("\\\\") || currentToken.equals("/")) { + File file = new File("."); + return checkChildren(file, "", cursor, false); + } + + // if the current token contains file separator character, then its parent directory can be extracted + if (currentToken.contains("\\\\") || currentToken.contains("/")) { + File file = new File(currentToken); + File parent = file.getParentFile(); + if ((file.isDirectory() && (currentToken.endsWith("/") || currentToken.endsWith("\\\\"))) + || parent == null) { + // the entered filename is a directory name, ending with file separator character - here + // all files in the directory will be returned as completion candidates; + // or, if parent is null, the file is in the root directory and + // the names of all files in this directory should be used to search for completion candidates + return checkChildren(file, "", cursor, false); + } else { + // there is a filename for completion, and the names of all files in the same directory will be used + // to search for completion candidates + return checkChildren(parent, file.getName(), cursor, false); + } + } + + // if the current token does not contain file separator character, + // then search for candidates in the current directory + return checkChildren(new File("."), currentToken, cursor, false); + } + + private Map<String, Integer> checkChildren(File parent, String nameToComplete, int cursor, boolean absolute) { + Map<String, Integer> result = new HashMap<String, Integer>(); + if(parent.exists()) { + File[] children = parent.listFiles(); + for(File child : children) { + if(child.getName().startsWith(nameToComplete)) { + if(absolute == true) { + result.put(child.getAbsolutePath(), cursor - nameToComplete.length()); + } else { + result.put(child.getName(), cursor - nameToComplete.length()); + } + } + } + } + return result; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/StringsCompleter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/StringsCompleter.java new file mode 100755 index 000000000..737237925 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/StringsCompleter.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.equinox.console.completion.common.Completer; + +/** + * This class provides completion for arbitrary strings + * + */ +public class StringsCompleter implements Completer { + + private Set<String> strings; + private boolean isCaseSensitive; + + public StringsCompleter(Set<String> strings, boolean isCaseSensitive) { + this.strings = strings; + this.isCaseSensitive = isCaseSensitive; + } + + public Map<String, Integer> getCandidates(String buffer, int cursor) { + String currentToken = CommandLineParser.getCurrentToken(buffer, cursor); + if (currentToken == null) { + return new HashMap<String, Integer>(); + } + if (!isCaseSensitive) { + currentToken = currentToken.toLowerCase(); + } + + int startIndex = cursor - currentToken.length(); + + // if currentToken is empty string, then there is nothing to complete + // the only exception is if the previous character is $, which signifies + // that a variable name is expected; in this case all strings will be + // returned as candidates + if(currentToken.equals("") && buffer.charAt(startIndex - 1) != '$') { + return new HashMap<String, Integer>(); + } + + Map<String, Integer> result = new HashMap<String, Integer>(); + + for(String candidate : strings) { + if (isCaseSensitive) { + if (candidate.startsWith(currentToken)) { + result.put(candidate, startIndex); + } + } else { + if (candidate.toLowerCase().startsWith(currentToken)) { + result.put(candidate, startIndex); + } + } + } + + return result; + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/VariableNamesCompleter.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/VariableNamesCompleter.java new file mode 100755 index 000000000..e72277de0 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/VariableNamesCompleter.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion; + +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.completion.common.Completer; + +/** + * This class provides completion for gogo session variables. + * + */ +public class VariableNamesCompleter implements Completer { + + private CommandSession session; + + public VariableNamesCompleter(CommandSession session) { + this.session = session; + } + + public Map<String, Integer> getCandidates(String buffer, int cursor) { + // CommandSession.get(null) returns the names of all registered varialbes + @SuppressWarnings("unchecked") + Set<String> variableNames = (Set<String>) session.get(null); + StringsCompleter completer = new StringsCompleter(variableNames, false); + return completer.getCandidates(buffer, cursor); + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/common/Completer.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/common/Completer.java new file mode 100755 index 000000000..d63b632ed --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/completion/common/Completer.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.completion.common; + +import java.util.Map; + +/** + * This is the interface for providing tab completion. + */ +public interface Completer { + /** + * Returns the possible candidates for completion for the passed string. + * + * @param buffer text to be completed + * @param cursor current position in the text + * @return map of candidate completions, and on which position in the buffer starts the completion + */ + public Map<String, Integer> getCandidates(String buffer, int cursor); +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/RolePrincipal.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/RolePrincipal.java new file mode 100755 index 000000000..c5f8c4ad4 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/RolePrincipal.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.jaas; + +import java.security.Principal; + +/** + * This class represents a user role + * + */ +public class RolePrincipal implements Principal { + private String roleName; + + public RolePrincipal(String roleName) { + this.roleName = roleName; + } + + public String getName() { + return roleName; + } + + public boolean equals(Object role) { + + if (role == null) { + return false; + } + + if (this == role) { + return true; + } + + if (!(role instanceof RolePrincipal)) { + return false; + } + + RolePrincipal otherRole = (RolePrincipal) role; + if (roleName != null) { + if (roleName.equals(otherRole.roleName)) { + return true; + } else { + return false; + } + } else { + if (otherRole.roleName == null) { + return true; + } else { + return false; + } + } + } + + @Override + public int hashCode() { + int result = 1; + result = 73 * result + (roleName == null ? 0 : roleName.hashCode()); + return result; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java new file mode 100755 index 000000000..941fb17e6 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/SecureStorageLoginModule.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.jaas; + +import java.io.IOException; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; + +/** + * This class implements a JAAS LoginModule, which performs username/password + * based authentication. It reads the user data from the store. + * + */ +public class SecureStorageLoginModule implements LoginModule { + + private volatile Subject subject; + private volatile CallbackHandler callbackHandler; + private volatile UserPrincipal userPrincipal; + private volatile boolean isSuccess; + + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map<String, ?> sharedState, Map<String, ?> options) { + this.subject = subject; + this.callbackHandler = callbackHandler; + } + + public boolean login() throws LoginException { + NameCallback nameCallback = new NameCallback("username: "); + PasswordCallback passwordCallback = new PasswordCallback("password: ", false); + try { + callbackHandler.handle(new Callback[]{nameCallback, passwordCallback}); + } catch (IOException e) { + throw new FailedLoginException("Cannot get username and password"); + } catch (UnsupportedCallbackException e) { + throw new FailedLoginException("Cannot get username and password"); + } + + String username = nameCallback.getName(); + char[] password = passwordCallback.getPassword(); + + userPrincipal = getUserInfo(username); + + try { + isSuccess = userPrincipal.authenticate(DigestUtil.encrypt(new String(password)).toCharArray()); + } catch (Exception e) { + throw new FailedLoginException("Wrong credentials"); + } + + if (isSuccess == true) { + return isSuccess; + } else { + throw new FailedLoginException("Wrong credentials"); + } + } + + public boolean commit() throws LoginException { + if (isSuccess == true) { + synchronized (this) { + subject.getPrincipals().add(userPrincipal); + subject.getPrincipals().addAll(userPrincipal.getRoles()); + } + return true; + } else { + userPrincipal.destroy(); + userPrincipal = null; + return false; + } + } + + public boolean abort() throws LoginException { + userPrincipal.destroy(); + userPrincipal = null; + return true; + } + + public boolean logout() throws LoginException { + synchronized (this) { + subject.getPrincipals().remove(userPrincipal); + subject.getPrincipals().removeAll(userPrincipal.getRoles()); + } + subject = null; + userPrincipal.destroy(); + userPrincipal = null; + return true; + } + + private UserPrincipal getUserInfo(String username) throws FailedLoginException { + try { + if (!SecureUserStore.existsUser(username)) { + throw new FailedLoginException("Wrong credentials"); + } + + String password = SecureUserStore.getPassword(username); + if (password == null) { + throw new FailedLoginException("Corrupted user"); + } + + String roles = SecureUserStore.getRoles(username); + if (roles == null) { + roles = ""; + } + + UserPrincipal userPrincipal = new UserPrincipal(username, password); + for (String role : roles.split(",")) { + userPrincipal.addRole(new RolePrincipal(role)); + } + + return userPrincipal; + } catch (Exception e) { + throw new FailedLoginException(e.getMessage()); + } + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/UserPrincipal.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/UserPrincipal.java new file mode 100755 index 000000000..7af4162a1 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/jaas/UserPrincipal.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.jaas; + +import java.security.Principal; +import java.util.HashSet; +import java.util.Set; + +/** + * This class represents a user with password and roles + * + */ +public class UserPrincipal implements Principal { + private String username; + private char[] password; + private Set<RolePrincipal> rolePrincipals = new HashSet<RolePrincipal>(); + + public UserPrincipal(String username, String password) { + this.username = username; + this.password = new char[password.length()]; + System.arraycopy(password.toCharArray(), 0, this.password, 0, this.password.length); + } + + public String getName() { + return username; + } + + public boolean authenticate(char[] password) { + if (password == null) { + return false; + } + + if (this.password == null) { + return false; + } + + if (this.password.length != password.length) { + return false; + } + + for(int i = 0; i < this.password.length; i++) { + if(this.password[i] != password[i]) { + return false; + } + } + + return true; + } + + public Set<RolePrincipal> getRoles() { + return rolePrincipals; + } + + public synchronized void addRole(RolePrincipal rolePrincipal) { + rolePrincipals.add(rolePrincipal); + } + + public boolean equals(Object userPrincipal) { + if (userPrincipal == null) { + return false; + } + + if (this == userPrincipal) { + return true; + } + + if (! (userPrincipal instanceof UserPrincipal)) { + return false; + } + + UserPrincipal otherUser = (UserPrincipal) userPrincipal; + if (username != null) { + if (!username.equals(otherUser.username)) { + return false; + } + } else { + if (otherUser.username != null) { + return false; + } + } + + if (password != null) { + if (otherUser.password == null) { + return false; + } + + if (password.length != otherUser.password.length) { + return false; + } + + for(int i = 0; i < password.length; i++) { + if (password[i] != otherUser.password[i]) { + return false; + } + } + } else { + if (otherUser.username != null) { + return false; + } + } + + if (rolePrincipals != null) { + if (!(rolePrincipals.equals(otherUser.rolePrincipals))) { + return false; + } + } else { + if (otherUser.rolePrincipals != null) { + return false; + } + } + + return true; + } + + public void destroy() { + for(int i = 0; i < password.length; i++) { + password[i] = ' '; + } + + password = null; + } + + @Override + public int hashCode() { + int result = 1; + result = 73 * result + (username == null ? 0 : username.hashCode()); + result = 73 * result + (password == null ? 0 : new String(password).hashCode()); + result = 73 * result + (rolePrincipals == null ? 0 : rolePrincipals.hashCode()); + return result; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshCommand.java new file mode 100755 index 000000000..136509a84 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshCommand.java @@ -0,0 +1,306 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.IOException; +import java.net.BindException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.Descriptor; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +/** + * This class implements a command for starting/stopping a simple ssh server. + * + */ +public class SshCommand { + private String defaultHost = null; + private int defaultPort; + private List<CommandProcessor> processors = new ArrayList<CommandProcessor>(); + private String host = null; + private int port; + private SshServ sshServ; + private BundleContext context; + private ServiceRegistration<?> configuratorRegistration; + private boolean isEnabled = false; + private final Object lock = new Object(); + + private static final String DEFAULT_USER = "equinox"; + private static final String DEFAULT_PASSWORD = "equinox"; + private static final String DEFAULT_USER_STORE_PROPERTY = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String HOST = "host"; + private static final String PORT = "port"; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String SSH_PID = "osgi.console.ssh"; + private static final String ENABLED = "enabled"; + + public SshCommand(CommandProcessor processor, BundleContext context) { + processors.add(processor); + this.context = context; + + if ("true".equals(context.getProperty(USE_CONFIG_ADMIN_PROP))) { + Dictionary<String, String> sshProperties = new Hashtable<String, String>(); + sshProperties.put(Constants.SERVICE_PID, SSH_PID); + try { + synchronized (lock) { + configuratorRegistration = context.registerService(ManagedService.class.getName(), new SshConfigurator(), sshProperties); + } + } catch (NoClassDefFoundError e) { + System.out.println("Configuration Admin not available!"); + return; + } + } else { + parseHostAndPort(); + } + } + + private void parseHostAndPort() { + String sshPort = null; + String consolePropValue = context.getProperty(SSH_PID); + if(consolePropValue != null) { + int index = consolePropValue.lastIndexOf(":"); + if (index > -1) { + defaultHost = consolePropValue.substring(0, index); + } + sshPort = consolePropValue.substring(index + 1); + isEnabled = true; + } + if (sshPort != null && !"".equals(sshPort)) { + try { + defaultPort = Integer.parseInt(sshPort); + } catch (NumberFormatException e) { + // do nothing + } + } + } + + public synchronized void start() { + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + properties.put("osgi.command.scope", "equinox"); + properties.put("osgi.command.function", new String[] {"ssh"}); + if ((port > 0 || defaultPort > 0) && isEnabled == true) { + try{ + ssh(new String[]{"start"}); + } catch (Exception e) { + System.out.println("Cannot start ssh. Reason: " + e.getMessage()); + e.printStackTrace(); + } + } + context.registerService(SshCommand.class.getName(), this, properties); + } + + @Descriptor("start/stop a ssh server") + public synchronized void ssh(String[] arguments) throws Exception { + String command = null; + String newHost = null; + int newPort = 0; + + for(int i = 0; i < arguments.length; i++) { + if("-?".equals(arguments[i]) || "-help".equals(arguments[i])) { + printHelp(); + return; + } else if("start".equals(arguments[i])) { + command = "start"; + } else if ("stop".equals(arguments[i])) { + command = "stop"; + } else if ("-port".equals(arguments[i]) && (arguments.length > i + 1)) { + i++; + newPort = Integer.parseInt(arguments[i]); + } else if ("-host".equals(arguments[i]) && (arguments.length > i + 1)) { + i++; + newHost = arguments[i]; + } else { + throw new Exception("Unrecognized ssh command/option " + arguments[i]); + } + } + + if (command == null) { + throw new Exception("No ssh command specified"); + } + + if (newPort != 0) { + port = newPort; + } else if (port == 0) { + port = defaultPort; + } + + if (port == 0) { + throw new Exception("No ssh port specified"); + } + + if (newHost != null) { + host = newHost; + } else { + host = defaultHost; + } + + if ("start".equals(command)) { + if (sshServ != null) { + throw new IllegalStateException("ssh is already running on port " + port); + } + + checkPortAvailable(port); + + try { + sshServ = new SshServ(processors, context, host, port); + } catch (NoClassDefFoundError e) { + // ssh server bundles are optional and may not be available + System.out.println("SSH bundles not available! If you want to use SSH, please install Apache sshd-core, Apache mina-core, slf4j-api and a slf4j logger implementation bundles"); + sshServ = null; + return; + } + sshServ.setName("equinox ssh"); + + if ("true".equals(context.getProperty(DEFAULT_USER_STORE_PROPERTY))) { + try { + checkUserStore(); + registerUserAdmin(); + } catch (NoClassDefFoundError e) { + System.out.println("If you want to use secure storage, please install Equinox security bundle and its dependencies"); + sshServ = null; + return; + } catch (IOException e) { + e.printStackTrace(); + sshServ = null; + return; + } + } + + try { + sshServ.start(); + } catch (RuntimeException e) { + sshServ = null; + return; + } + } else if ("stop".equals(command)) { + if (sshServ == null) { + throw new IllegalStateException("ssh is not running."); + } + + sshServ.stopSshServer(); + sshServ = null; + } + } + + public synchronized void addCommandProcessor(CommandProcessor processor) { + processors.add(processor); + sshServ.addCommandProcessor(processor); + } + + public synchronized void removeCommandProcessor(CommandProcessor processor) { + processors.remove(processor); + sshServ.removeCommandProcessor(processor); + } + + private void checkPortAvailable(int port) throws Exception { + ServerSocket socket = null; + try { + socket = new ServerSocket(port); + } catch (BindException e) { + throw new Exception ("Port " + port + " already in use"); + } finally { + if (socket != null) { + socket.close(); + } + } + } + + /* + * Register user administration commands + */ + private void registerUserAdmin() { + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + properties.put("osgi.command.scope", "equinox"); + properties.put("osgi.command.function", new String[] {"addUser", "addUser", "deleteUser", "resetPassword", "setPassword", "addRoles", "removeRoles", "listUsers"}); + context.registerService(UserAdminCommand.class.getName(), new UserAdminCommand(), properties); + } + + /* + * Create user store if not available. Add the default user, if there is no other user in the store. + */ + private void checkUserStore() throws Exception { + SecureUserStore.initStorage(); + if(SecureUserStore.getUserNames().length == 0) { + SecureUserStore.putUser(DEFAULT_USER, DigestUtil.encrypt(DEFAULT_PASSWORD), null ); + } + } + + private void printHelp() { + StringBuffer help = new StringBuffer(); + help.append("ssh - start simple ssh server"); + help.append("\n"); + help.append("Usage: ssh start | stop [-port port] [-host host]"); + help.append("\n"); + help.append("\t"); + help.append("-port"); + help.append("\t"); + help.append("listen port (default="); + help.append(defaultPort); + help.append(")"); + help.append("\n"); + help.append("\t"); + help.append("-host"); + help.append("\t"); + help.append("local host address to listen on (default is none - listen on all network interfaces)"); + help.append("\n"); + help.append("\t"); + help.append("-?, -help"); + help.append("\t"); + help.append("show help"); + System.out.println(help.toString()); + } + + class SshConfigurator implements ManagedService { + @SuppressWarnings("rawtypes") + private Dictionary properties; + @SuppressWarnings({ "rawtypes", "unchecked" }) + public synchronized void updated(Dictionary props) throws ConfigurationException { + if (props != null) { + this.properties = props; + properties.put(Constants.SERVICE_PID, SSH_PID); + } else { + return; + } + + defaultPort = Integer.parseInt(((String)properties.get(PORT))); + defaultHost = (String)properties.get(HOST); + if (properties.get(ENABLED) == null) { + isEnabled = false; + } else { + isEnabled = Boolean.parseBoolean((String)properties.get(ENABLED)); + } + synchronized (lock) { + configuratorRegistration.setProperties(properties); + } + if (sshServ == null && isEnabled == true) { + try { + ssh(new String[]{"start"}); + } catch (Exception e) { + System.out.println("Cannot start ssh: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputHandler.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputHandler.java new file mode 100755 index 000000000..d1b2eb766 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputHandler.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.InputStream; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.InputHandler; + +/** + * This class customizes the generic handler with a concrete content processor, + * which provides ssh protocol handling. + * + */ +public class SshInputHandler extends InputHandler { + public SshInputHandler(InputStream input, ConsoleInputStream in, ConsoleOutputStream out) { + super(input, in, out); + inputScanner = new SshInputScanner(in, out); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputScanner.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputScanner.java new file mode 100755 index 000000000..f0c88a934 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshInputScanner.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.Scanner; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; + +/** + * This class performs preprocessing of the input from the ssh server in order to echo the visible + * characters back to the console. + * + */ +public class SshInputScanner extends Scanner { + + public SshInputScanner(ConsoleInputStream toShell, OutputStream toTelnet) { + super(toShell, toTelnet); + TerminalTypeMappings currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE); + currentEscapesToKey = currentMapping.getEscapesToKey(); + escapes = currentMapping.getEscapes(); + setBackspace(currentMapping.getBackspace()); + setDel(currentMapping.getDel()); + } + + @Override + public void scan(int b) throws IOException { + b &= 0xFF; + + if (isEsc) { + scanEsc(b); + } 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}); + } + } + } + + @Override + 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshServ.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshServ.java new file mode 100755 index 000000000..a8cfed62e --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshServ.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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.IOException; +import java.util.List; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.sshd.SshServer; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.jaas.JaasPasswordAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.osgi.framework.BundleContext; + +/** + * This class configures and start an ssh server + * + */ +public class SshServ extends Thread { + private int port; + private String host; + private SshServer sshServer = null; + private SshShellFactory shellFactory = null; + + private static final String SSH_KEYSTORE_PROP = "ssh.server.keystore"; + private static final String SSH_KEYSTORE_PROP_DEFAULT = "hostkey.ser"; + private static final String EQUINOX_CONSOLE_DOMAIN = "equinox_console"; + + public SshServ(List<CommandProcessor> processors, BundleContext context, String host, int port) { + this.host = host; + this.port = port; + shellFactory = new SshShellFactory(processors, context); + } + + public void run() throws RuntimeException { + try { + sshServer = SshServer.setUpDefaultServer(); + } catch (NoClassDefFoundError e1) { + System.out.println("SSH bundles not available! If you want to use SSH, please install Apache sshd-core, Apache mina-core, slf4j-api and a slf4j logger implementation bundles"); + throw new RuntimeException("SSH bundles not available"); + } + if (host != null) { + sshServer.setHost(host); + } + sshServer.setPort(port); + sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(System.getProperty(SSH_KEYSTORE_PROP, SSH_KEYSTORE_PROP_DEFAULT))); + sshServer.setShellFactory(shellFactory); + sshServer.setPasswordAuthenticator(createJaasPasswordAuthenticator()); + try { + sshServer.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public synchronized void stopSshServer() { + try { + sshServer.stop(true); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public synchronized void addCommandProcessor(CommandProcessor processor) { + shellFactory.addCommandProcessor(processor); + } + + public synchronized void removeCommandProcessor(CommandProcessor processor) { + shellFactory.removeCommandProcessor(processor); + } + + private PasswordAuthenticator createJaasPasswordAuthenticator() { + JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator(); + jaasPasswordAuthenticator.setDomain(EQUINOX_CONSOLE_DOMAIN); + return jaasPasswordAuthenticator; + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshSession.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshSession.java new file mode 100755 index 000000000..fe78733c5 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshSession.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ +package org.eclipse.equinox.console.ssh; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.eclipse.equinox.console.supportability.ConsoleInputHandler; +import org.eclipse.equinox.console.supportability.ConsoleInputScanner; +import org.osgi.framework.BundleContext; + +/** + * This class manages a ssh connection. It is responsible for wrapping the original io streams + * from the socket, and starting a CommandSession to execute commands from the ssh. + * + */ +public class SshSession extends Thread implements Closeable { + private CommandProcessor processor; + private BundleContext context; + private SshShell sshShell; + private InputStream in; + private OutputStream out; + private TerminalTypeMappings currentMappings; + private Map<String, KEYS> currentEscapesToKey; + + private static final String PROMPT = "prompt"; + private static final String OSGI_PROMPT = "osgi> "; + private static final String SCOPE = "SCOPE"; + private static final String EQUINOX_SCOPE = "equinox:*"; + private static final String INPUT_SCANNER = "INPUT_SCANNER"; + private static final String SSH_INPUT_SCANNER = "SSH_INPUT_SCANNER"; + private static final String USER_STORAGE_PROPERTY_NAME = "osgi.console.ssh.useDefaultSecureStorage"; + private static final String DEFAULT_USER = "equinox"; + private static final String CLOSEABLE = "CLOSEABLE"; + private static final int ADD_USER_COUNTER_LIMIT = 2; + + public SshSession(CommandProcessor processor, BundleContext context, SshShell sshShell, InputStream in, OutputStream out, TerminalTypeMappings currentMappings, Map<String, KEYS> currentExcapesToKey) { + this.processor = processor; + this.context = context; + this.sshShell = sshShell; + this.in = in; + this.out = out; + this.currentMappings = currentMappings; + this.currentEscapesToKey = currentExcapesToKey; + } + + public void run() { + ConsoleInputStream input = new ConsoleInputStream(); + ConsoleOutputStream outp = new ConsoleOutputStream(out); + SshInputHandler inputHandler = new SshInputHandler(in, input, outp); + inputHandler.getScanner().setBackspace(currentMappings.getBackspace()); + inputHandler.getScanner().setDel(currentMappings.getDel()); + inputHandler.getScanner().setCurrentEscapesToKey(currentEscapesToKey); + inputHandler.getScanner().setEscapes(currentMappings.getEscapes()); + inputHandler.start(); + + ConsoleInputStream inp = new ConsoleInputStream(); + ConsoleInputHandler consoleInputHandler = new ConsoleInputHandler(input, inp, outp); + consoleInputHandler.getScanner().setBackspace(currentMappings.getBackspace()); + consoleInputHandler.getScanner().setDel(currentMappings.getDel()); + consoleInputHandler.getScanner().setCurrentEscapesToKey(currentEscapesToKey); + consoleInputHandler.getScanner().setEscapes(currentMappings.getEscapes()); + ((ConsoleInputScanner)consoleInputHandler.getScanner()).setContext(context); + consoleInputHandler.start(); + + final CommandSession session; + final PrintStream output = new PrintStream(outp); + + session = processor.createSession(inp, output, output); + session.put(SCOPE, EQUINOX_SCOPE); + session.put(PROMPT, OSGI_PROMPT); + session.put(INPUT_SCANNER, consoleInputHandler.getScanner()); + session.put(SSH_INPUT_SCANNER, inputHandler.getScanner()); + // Store this closeable object in the session, so that the disconnect command can close it + session.put(CLOSEABLE, this); + ((ConsoleInputScanner)consoleInputHandler.getScanner()).setSession(session); + + try { + if ("true".equals(context.getProperty(USER_STORAGE_PROPERTY_NAME))) { + String[] names = SecureUserStore.getUserNames(); + for (String name : names) { + // if the default user is the only user, request creation of a new user and delete the default + if (DEFAULT_USER.equals(name)) { + if (names.length == 1) { + session.getConsole().println("Currently the default user is the only one; since it will be deleted after first login, create a new user:"); + boolean isUserAdded =false; + int count = 0; + while (!isUserAdded && count < ADD_USER_COUNTER_LIMIT ){ + isUserAdded = ((Boolean) session.execute("addUser")).booleanValue(); + count++; + } + if (!isUserAdded) { + break; + } + } + if (SecureUserStore.existsUser(name)) { + SecureUserStore.deleteUser(name); + } + break; + } + } + } + session.execute("gosh --login --noshutdown"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + session.close(); + } + + } + + public void close() throws IOException { + this.interrupt(); + sshShell.removeSession(this); + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShell.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShell.java new file mode 100755 index 000000000..f1f4839e3 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShell.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings; +import org.eclipse.equinox.console.common.terminal.VT320TerminalTypeMappings; +import org.osgi.framework.BundleContext; + +/** + * This class manages a ssh connection. It is responsible for starting a sessions to execute commands + * from the ssh. If there are multiple CommandProcessors, a session is started for each of them. + * + */ +public class SshShell implements Command { + + private List<CommandProcessor> processors; + private BundleContext context; + private InputStream in; + private OutputStream out; + private ExitCallback callback; + private Map<CommandProcessor, SshSession> commandProcessorToConsoleThreadMap = new HashMap<CommandProcessor, SshSession>(); + + private final Map<String, TerminalTypeMappings> supportedEscapeSequences; + private static final String DEFAULT_TTYPE = File.separatorChar == '/' ? "XTERM" : "ANSI"; + private TerminalTypeMappings currentMappings; + private Map<String, KEYS> currentEscapesToKey; + private static final String TERMINAL_PROPERTY = "TERM"; + + public SshShell(List<CommandProcessor> processors, BundleContext context) { + this.processors = processors; + this.context = context; + supportedEscapeSequences = new HashMap<String, TerminalTypeMappings> (); + supportedEscapeSequences.put("ANSI", new ANSITerminalTypeMappings()); + supportedEscapeSequences.put("WINDOWS", new ANSITerminalTypeMappings()); + supportedEscapeSequences.put("VT100", new VT100TerminalTypeMappings()); + VT220TerminalTypeMappings vtMappings = new VT220TerminalTypeMappings(); + supportedEscapeSequences.put("VT220", vtMappings); + supportedEscapeSequences.put("XTERM", vtMappings); + supportedEscapeSequences.put("VT320", new VT320TerminalTypeMappings()); + supportedEscapeSequences.put("SCO", new SCOTerminalTypeMappings()); + + currentMappings = supportedEscapeSequences.get(DEFAULT_TTYPE); + currentEscapesToKey = currentMappings.getEscapesToKey(); + } + + public void setInputStream(InputStream in) { + this.in = in; + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setErrorStream(OutputStream err) { + // do nothing + } + + public void setExitCallback(ExitCallback callback) { + this.callback = callback; + } + + public synchronized void start(Environment env) throws IOException { + String term = env.getEnv().get(TERMINAL_PROPERTY); + TerminalTypeMappings mapping = supportedEscapeSequences.get(term.toUpperCase()); + if(mapping != null) { + currentMappings = mapping; + currentEscapesToKey = mapping.getEscapesToKey(); + } + + for (CommandProcessor processor : processors) { + createNewSession(processor); + } + } + + public synchronized void addCommandProcessor(CommandProcessor processor) { + createNewSession(processor); + } + + public synchronized void removeCommandProcessor(CommandProcessor processor) { + Thread consoleSession = commandProcessorToConsoleThreadMap.get(processor); + if (consoleSession != null) { + consoleSession.interrupt(); + } + } + + private void createNewSession(CommandProcessor processor) { + SshSession consoleSession = startNewConsoleSession(processor); + commandProcessorToConsoleThreadMap.put(processor, consoleSession); + } + + public void destroy() { + return; + } + + public void onExit() { + if (commandProcessorToConsoleThreadMap.values() != null) { + for (Thread consoleSession : commandProcessorToConsoleThreadMap.values()) { + consoleSession.interrupt(); + } + } + callback.onExit(0); + } + + public void removeSession(SshSession session) { + CommandProcessor processorToRemove = null; + for (CommandProcessor processor : commandProcessorToConsoleThreadMap.keySet()) { + if (session.equals(commandProcessorToConsoleThreadMap.get(processor))) { + processorToRemove = processor; + break; + } + } + + if (processorToRemove != null) { + commandProcessorToConsoleThreadMap.remove(processorToRemove); + } + + if (commandProcessorToConsoleThreadMap.size() == 0) { + onExit(); + } + } + + private SshSession startNewConsoleSession(CommandProcessor processor) { + SshSession consoleSession = new SshSession(processor, context, this, in, out, currentMappings, currentEscapesToKey); + consoleSession.start(); + return consoleSession; + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShellFactory.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShellFactory.java new file mode 100755 index 000000000..82b7964d9 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/SshShellFactory.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.sshd.common.Factory; +import org.apache.sshd.server.Command; +import org.osgi.framework.BundleContext; + +/** + * Shell factory used by the SSH server to create a SSH shell + * + */ +public class SshShellFactory implements Factory<Command> { + + private List<CommandProcessor> processors; + private BundleContext context; + private Set<SshShell> shells = new HashSet<SshShell>(); + + public SshShellFactory(List<CommandProcessor> processors, BundleContext context) { + this.processors = processors; + this.context = context; + } + + public synchronized Command create() { + SshShell shell = new SshShell(processors, context); + shells.add(shell); + return shell; + } + + public synchronized void addCommandProcessor (CommandProcessor processor) { + processors.add(processor); + for (SshShell shell : shells) { + shell.addCommandProcessor(processor); + } + } + + public synchronized void removeCommandProcessor (CommandProcessor processor) { + processors.remove(processor); + for (SshShell shell : shells) { + shell.removeCommandProcessor(processor); + } + } + + public void exit() { + for(SshShell shell : shells) { + shell.onExit(); + } + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java new file mode 100755 index 000000000..4a57da6be --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/ssh/UserAdminCommand.java @@ -0,0 +1,447 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.ssh; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.felix.service.command.CommandSession; +import org.apache.felix.service.command.Descriptor; +import org.eclipse.equinox.console.common.Scanner; +import org.eclipse.equinox.console.storage.DigestUtil; +import org.eclipse.equinox.console.storage.SecureUserStore; +import org.eclipse.equinox.console.supportability.ConsoleInputScanner; + +/** + * This class provides commands for administering users: adding, removing and listing users; setting or changing password; + * resetting password; adding and removing roles + * + * + */ +public class UserAdminCommand { + private static final String INPUT_SCANNER = "INPUT_SCANNER"; + private static final String SSH_INPUT_SCANNER = "SSH_INPUT_SCANNER"; + private static final String DEFAULT_USER = "equinox"; + private static final int MINIMAL_PASSWORD_LENGTH = 8; + private static final int PASSWORD_INPUT_TRIALS_LIMIT = 3; + + /** + * Command for adding a user + * + * @param args command line arguments in the format -username <username> -password <password> -roles <comma-separated list of user roles (optional)> + * @throws Exception + */ + @Descriptor("Add user with password and roles") + public void addUser(@Descriptor("-username <username>\r\n-password <password>\r\n-roles <comma-separated list of user roles (optional)>") String[] args) throws Exception { + String username = null; + String password = null; + String roles = ""; + + for (int i = 0; i < args.length; i++) { + if ("-username".equals(args[i]) && i < args.length - 1) { + username = args[i + 1]; + i++; + } else if ("-password".equals(args[i]) && i < args.length - 1) { + password = args[i + 1]; + i++; + } else if ("-roles".equals(args[i]) && i < args.length - 1) { + roles = args[i + 1]; + i++; + } + } + + if (! validateUsername(username)) { + throw new Exception("Invalid username"); + } + + if (password == null) { + throw new Exception("Password not specified"); + } + + if (password.length() < MINIMAL_PASSWORD_LENGTH) { + throw new Exception("Password should be at least 8 symblos"); + } + + SecureUserStore.putUser(username, DigestUtil.encrypt(password), roles); + + if(SecureUserStore.existsUser(DEFAULT_USER)) { + SecureUserStore.deleteUser(DEFAULT_USER); + } + } + + /** + * Command for setting or changing the password of a user. + * + * @param args command-line arguments in the format -username <username> -password <password> + * @throws Exception + */ + @Descriptor("Set or change password") + public void setPassword(@Descriptor("-username <username>\r\n-password <password>") String[] args) throws Exception { + String username = null; + String password = null; + + for (int i = 0; i < args.length; i++) { + if ("-username".equals(args[i]) && i < args.length - 1) { + username = args[i + 1]; + i++; + } else if ("-password".equals(args[i]) && i < args.length - 1) { + password = args[i + 1]; + i++; + } + } + + if (! validateUsername(username)) { + throw new Exception("Invalid username"); + } + + if (password == null) { + throw new Exception("Password not specified"); + } + + if (password.length() < MINIMAL_PASSWORD_LENGTH) { + throw new Exception("Password should be at least 8 symblos"); + } + + SecureUserStore.setPassword(username, DigestUtil.encrypt(password)); + } + + /** + * Command for adding a user. The command interactively asks for username, password and roles; the + * input plain text password is encrypted before storing. + * + * @param session + * @return true if the user was successfully added + * + * @throws Exception + */ + @Descriptor("Add user with password and roles interactively") + public boolean addUser(final CommandSession session) throws Exception { + + ConsoleInputScanner inputScanner = (ConsoleInputScanner) session.get(INPUT_SCANNER); + Scanner scanner = (Scanner) session.get(SSH_INPUT_SCANNER); + + try { + // switch off the history so that username, password and roles will not be saved in console history + if (scanner != null) { + inputScanner.toggleHistoryEnabled(false); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String username = readUsername(reader); + if (!validateUsername(username)) { + System.out.println("Invalid username"); + return false; + } + + if (SecureUserStore.existsUser(username)) { + System.out.println("Username already exists"); + return false; + } + + // switch off the echo so that the password will not be printed in the console + if (scanner != null) { + scanner.toggleEchoEnabled(false); + } + String password = readPassword(reader); + if (password == null){ + return false; + } + if (scanner != null) { + scanner.toggleEchoEnabled(true); + } + + String roles = readRoles(reader); + if (roles == null) { + return false; + } + + SecureUserStore.putUser(username, DigestUtil.encrypt(password), roles); + + if(SecureUserStore.existsUser(DEFAULT_USER)) { + SecureUserStore.deleteUser(DEFAULT_USER); + } + } finally { + if (scanner != null) { + inputScanner.toggleHistoryEnabled(true); + scanner.toggleEchoEnabled(true); + } + } + + return true; + } + + @Descriptor("Delete user") + public void deleteUser(@Descriptor("username of the user to be deleted") String username) throws Exception { + if (SecureUserStore.existsUser(username)) { + SecureUserStore.deleteUser(username); + } + } + + /** + * Command to remove the password for a user + * + * @param username user to remove the password for + * @throws Exception + */ + @Descriptor("Reset password") + public void resetPassword(@Descriptor("username of the user whose password will be reset") String username) throws Exception { + if (!SecureUserStore.existsUser(username)) { + throw new Exception("Such user does not exist"); + } + + SecureUserStore.resetPassword(username); + } + + /** + * Command to set or change the password for a user; the command asks interactively for the new password; the + * input plain text password is encrypted before storing. + * + * @param session + * @param username the user whose password will be changed + * @throws Exception + */ + @Descriptor("Set or change password") + public void setPassword(final CommandSession session, @Descriptor("Username of the user whose password will be changed") String username) throws Exception { + if ("".equals(username)) { + System.out.println("Username not specified"); + return; + } + + if (!SecureUserStore.existsUser(username)) { + throw new Exception("Such user does not exist"); + } + + ConsoleInputScanner inputScanner = (ConsoleInputScanner) session.get(INPUT_SCANNER); + Scanner scanner = (Scanner) session.get(SSH_INPUT_SCANNER); + + try { + // switch off echo and history so that the password is neither echoed to the console, nor saved in history + if (scanner != null) { + inputScanner.toggleHistoryEnabled(false); + scanner.toggleEchoEnabled(false); + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String password = readPassword(reader); + if (password == null) { + return; + } + + SecureUserStore.setPassword(username, DigestUtil.encrypt(password)); + } finally { + if (scanner != null) { + inputScanner.toggleHistoryEnabled(true); + scanner.toggleEchoEnabled(true); + } + } + } + + /** + * Command to add roles to a user + * + * @param args command line arguments in the format -username <username>\r\n-roles <comma-separated list of roles to add> + * @throws Exception + */ + @Descriptor("Add roles to user") + public void addRoles(@Descriptor("-username <username>\r\n-roles <comma-separated list of roles to add>") String[] args) throws Exception { + String username = null; + String roles = ""; + + for (int i = 0; i < args.length; i++) { + if ("-username".equals(args[i]) && i < args.length - 1) { + username = args[i + 1]; + i++; + } else if ("-roles".equals(args[i]) && i < args.length - 1) { + roles = args[i + 1]; + i++; + } + } + + if (username == null) { + throw new Exception("Username not specified"); + } + + if("".equals(roles)) { + return; + } + + if (!SecureUserStore.existsUser(username)) { + throw new Exception("Such user does not exist"); + } + + SecureUserStore.addRoles(username, roles); + } + + /** + * Command to remove roles for a particular user + * + * @param args command line arguments in the format -username <username>\r\n-roles <comma-separated list of roles to remove> + * @throws Exception + */ + @Descriptor("Remove user roles") + public void removeRoles(@Descriptor("-username <username>\r\n-roles <comma-separated list of roles to remove>") String[] args) throws Exception { + String username = null; + String roles = ""; + + for (int i = 0; i < args.length; i++) { + if ("-username".equals(args[i]) && i < args.length - 1) { + username = args[i + 1]; + i++; + } else if ("-roles".equals(args[i]) && i < args.length - 1) { + roles = args[i + 1]; + i++; + } + } + + if (username == null) { + throw new Exception("Username not specified"); + } + + if("".equals(roles)) { + return; + } + + if (!SecureUserStore.existsUser(username)) { + throw new Exception("Such user does not exist"); + } + + SecureUserStore.removeRoles(username, roles); + } + + /** + * Command to list available users + * + * @throws Exception + */ + @Descriptor("Lists available users") + public void listUsers() throws Exception { + + String[] users = SecureUserStore.getUserNames(); + + if(users.length == 0) { + System.out.println("No users available"); + return; + } + + for(String user : users) { + System.out.println(user); + } + } + + private String readPassword(BufferedReader reader) { + String password = null; + int count = 0; + + while (password == null && count < PASSWORD_INPUT_TRIALS_LIMIT){ + System.out.print("password: "); + System.out.flush(); + + try { + password = reader.readLine(); + } catch (IOException e) { + System.out.println("Error while reading password"); + return null; + } + + + if (password == null || "".equals(password)) { + System.out.println("Password not specified"); + password = null; + } else if (password.length() < MINIMAL_PASSWORD_LENGTH) { + System.out.println("Password should be at least 8 symblos"); + password = null; + } + + count++; + } + + if (password == null) { + return null; + } + + String passwordConfirmation = null; + count = 0; + + while (passwordConfirmation == null && count < PASSWORD_INPUT_TRIALS_LIMIT){ + System.out.print("Confirm password: "); + System.out.flush(); + + try { + passwordConfirmation = reader.readLine(); + if (!password.equals(passwordConfirmation)) { + System.out.println("The passwords do not match!"); + passwordConfirmation = null; + } + } catch (IOException e) { + System.out.println("Error while reading password"); + return null; + } + + count++; + } + if (passwordConfirmation == null){ + return null; + } + return password; + } + + private String readUsername (BufferedReader reader) { + System.out.print("username: "); + System.out.flush(); + String username = null; + + try { + username = reader.readLine(); + } catch (IOException e) { + System.out.println("Error while reading username"); + return null; + } + + if (username == null || "".equals(username)) { + System.out.println("Username not specified"); + return null; + } + + return username; + } + + private String readRoles (BufferedReader reader){ + //roles input validation + System.out.print("roles: "); + System.out.flush(); + String roles = null; + try { + roles = reader.readLine(); + } catch (IOException e) { + System.out.println("Error while reading roles"); + return null; + } + + if (roles == null) { + roles = ""; + } + return roles; + } + + private static boolean validateUsername (String username){ + if( username == null){ + return false; + }else{ + Pattern allowedChars = Pattern.compile("[A-Za-z0-9_.]+"); + Matcher matcher = allowedChars.matcher(username); + return matcher.matches(); + } + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/DigestUtil.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/DigestUtil.java new file mode 100755 index 000000000..fec76ba7d --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/DigestUtil.java @@ -0,0 +1,69 @@ +package org.eclipse.equinox.console.storage; + +import java.security.MessageDigest; +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +import java.security.NoSuchAlgorithmException; + +/** + * This class provides utility method for one-way hashing of strings + * + */ +public class DigestUtil { + private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + private static final String MD5 = "MD5"; + private static final String SHA1 = "SHA1"; + + /** + * Create a one-way hash of an input strings. First a MD5 hash of the input string + * is calculated and appended to the string, and then the new string is hashed with SHA1 + * + * @param originalText the string to be hashed + * @return hashed string + * @throws Exception + */ + public static String encrypt(String originalText)throws Exception{ + try { + String password_salt = appendSalt(originalText); + byte[] sha_digest; + + sha_digest = getDigest(password_salt.getBytes(), SHA1); + return asHex(sha_digest); + } catch (NoSuchAlgorithmException e) { + throw new Exception ("Encryption Failed!"); + } + } + + private static String appendSalt(String inputPassword) throws NoSuchAlgorithmException{ + byte [] salt = getDigest(inputPassword.getBytes(), MD5); + return inputPassword + asHex(salt); + } + + //byte array into hexademical string + private static String asHex(byte[] buf) + { + char[] chars = new char[2 * buf.length]; + for (int i = 0; i < buf.length; ++i) + { + chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4]; + chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F]; + } + return new String(chars); + } + + //generate digest byte[] + private static byte[] getDigest(byte[] inputData, String algorithm) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + md.update(inputData); + return md.digest(); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/SecureUserStore.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/SecureUserStore.java new file mode 100755 index 000000000..dc6bde77f --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/storage/SecureUserStore.java @@ -0,0 +1,614 @@ +/******************************************************************************* + * 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.storage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +/** + * This class implements a storage for users, passwords and roles. The data is stored in a + * properties-like file in the format /ssh/<username>/password=<password> and + * /ssh/<username>/roles=<comma_separated_list_of_roles> + * + * + */ +public class SecureUserStore { + + private static final String USER_STORE_FILE_NAME = "org.eclipse.equinox.console.jaas.file"; + private static final String PASSWORD_KEY = "password"; + private static final String ROLES_KEY = "roles"; + private static final String SSH_PREFIX = "/ssh"; + private static final String DELIMITER = "/"; + private static final int USERNAME_INDEX = 2; + private static final int KEY_ELEMENTS_COUNT = 4; + + /** + * Gets the usernames of all users. + * + * @return String array containing the usernames + */ + public static String[] getUserNames() { + String userFileLoc = null; + InputStream in = null; + + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + Set<String> userNames = new HashSet<String>(); + for (Object key : users.keySet()) { + if (!(key instanceof String)) { + continue; + } + String[] parts = ((String) key).split(DELIMITER); + // since the key starts with DELIMITER, the first element of key.split(DELIMITER) is an empty string + // that is why the result is {"", "ssh", "<username>", "password"} or {"", "ssh", "<username>", "roles"} + if (parts.length < KEY_ELEMENTS_COUNT) { + continue; + } + userNames.add(parts[USERNAME_INDEX]); + } + + return userNames.toArray(new String[0]); + + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + } + } + + public static String getPassword(String username) { + return getProperty(username, PASSWORD_KEY); + } + + public static String getRoles(String username) { + return getProperty(username, ROLES_KEY); + } + + /** + * Stores a user entry to the store. + * + * @param username the name of the user + * @param password the password of the user + * @param roles comma-separated list of the roles of the user + */ + public static void putUser(String username, String password, String roles) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + if (existsUser(username, users)){ + throw new IllegalArgumentException("The user already exists!"); + } + + if (roles == null) { + roles = ""; + } + + String userPassKey = constructPropertyName(username, PASSWORD_KEY); + String userRolesKey = constructPropertyName(username, ROLES_KEY); + users.put(userPassKey, password); + users.put(userRolesKey, roles); + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Adds roles for a particular user + * + * @param username user to add roles to + * @param roles comma-separated list of new roles for the user + */ + public static void addRoles(String username, String roles) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + if (roles == null || roles.length() == 0) { + return; + } + + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + String userRolesKey = constructPropertyName(username, ROLES_KEY); + String currentRoles = (String)users.remove(userRolesKey); + Set<String> rolesSet = new HashSet<String>(); + + if (currentRoles.length() > 0) { + for (String role : currentRoles.split(",")) { + rolesSet.add(role); + } + } + + for (String role : roles.split(",")) { + rolesSet.add(role); + } + + StringBuilder builder = new StringBuilder(); + for (String role : rolesSet) { + builder.append(role); + builder.append(","); + } + builder.deleteCharAt(builder.lastIndexOf(",")); + + users.put(userRolesKey, builder.toString()); + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Removes roles from a user + * + * @param username user to remove roles from + * @param rolesToRemove comma-separated list of roles to be removed + */ + public static void removeRoles(String username, String rolesToRemove) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + if(rolesToRemove == null || rolesToRemove.length() == 0) { + return; + } + + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + String userRolesKey = constructPropertyName(username, ROLES_KEY); + String currentRoles = (String)users.remove(userRolesKey); + Set<String> rolesSet = new HashSet<String>(); + + for (String role : currentRoles.split(",")) { + rolesSet.add(role); + } + + for (String role : rolesToRemove.split(",")) { + rolesSet.remove(role); + } + + StringBuilder builder = new StringBuilder(); + for (String role : rolesSet) { + builder.append(role); + builder.append(","); + } + builder.deleteCharAt(builder.lastIndexOf(",")); + + users.put(userRolesKey, builder.toString()); + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Removes an entry for the user from the store. + * + * @param username user to be removed + */ + public static void deleteUser(String username) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + if (!existsUser(username, users)){ + throw new IllegalArgumentException("The user does not exist!"); + } + +// Set<Object> keys = users.keySet(); +// for (Object key : keys) { +// if ((key instanceof String) && ((String) key).contains(DELIMITER + username + DELIMITER)) { +// users.remove(key); +// } +// } + String rolesProperty = constructPropertyName(username, ROLES_KEY); + String passwordProperty = constructPropertyName(username, PASSWORD_KEY); + + users.remove(rolesProperty); + users.remove(passwordProperty); + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Removes the password for a user + * + * @param username user to reset the password + */ + public static void resetPassword(String username) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + if (!existsUser(username, users)){ + throw new IllegalArgumentException("The user does not exist!"); + } + + for (Object key : users.keySet()) { + if (key instanceof String && ((String) key).contains(DELIMITER + username + DELIMITER + PASSWORD_KEY)) { + users.remove(key); + break; + } + } + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Sets or changes the password for a user + * + * @param username user to set tha password for + * @param password the new password + */ + public static void setPassword(String username, String password) { + String userFileLoc = null; + InputStream in = null; + OutputStream out = null; + + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + if (!existsUser(username, users)){ + throw new IllegalArgumentException("The user does not exist!"); + } + + String passwordPropertyName = constructPropertyName(username, PASSWORD_KEY); + for (Object key : users.keySet()) { + if ((key instanceof String) && ((String) key).contains(passwordPropertyName)) { + users.remove(key); + break; + } + } + + users.put(passwordPropertyName, password); + + out = new FileOutputStream(userFileLoc); + try { + users.store(out, null); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot store properties in file " + userFileLoc); + } + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * CHecks if an entry for a user exists in the store + * + * @param username user to check + * @return true if there is an entry for this user in the store, false otherwise + */ + public static boolean existsUser(String username) { + String userFileLoc = null; + InputStream in = null; + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + return existsUser(username, users); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + } + } + + /** + * Creates the store file if it does not exist + * + * @throws IOException + */ + public static void initStorage() throws IOException { + String userFileLoc = getFileLocation(); + File file = new File(userFileLoc); + if (!file.exists()) { + OutputStream out = null; + try { + Properties props = new Properties(); + out = new FileOutputStream(file); + props.store(out, null); + } finally { + if (out != null) { + out.close(); + } + } + } + } + + private static String getProperty(String username, String propertyName) { + String userFileLoc = null; + InputStream in = null; + try { + userFileLoc = getFileLocation(); + in = new FileInputStream(userFileLoc); + Properties users = null; + + try { + users = populateUserStore(in); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load properties from file " + userFileLoc); + } + + return users.getProperty(constructPropertyName(username, propertyName)); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("File " + userFileLoc + " does not exist"); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + //do nothing + } + } + } + } + + private static Properties populateUserStore(InputStream in) throws IOException { + Properties userProperties = new Properties(); + userProperties.load(in); + return userProperties; + } + + private static String getFileLocation(){ + String userFileLoc = System.getProperty(USER_STORE_FILE_NAME); + if (userFileLoc == null) { + throw new IllegalArgumentException("Property " + USER_STORE_FILE_NAME + " is not set; cannot use JAAS authentication"); + } + + return userFileLoc; + } + + private static String constructPropertyName(String user, String propertyName) { + StringBuilder builder = new StringBuilder(); + builder.append(SSH_PREFIX); + builder.append(DELIMITER); + builder.append(user); + builder.append(DELIMITER); + builder.append(propertyName); + return builder.toString(); + } + + private static boolean existsUser(String username, Properties users) { + for (Object user : users.keySet()) { + if (user instanceof String && ((String) user).contains(DELIMITER + username + DELIMITER)) { + return true; + } + } + return false; + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputHandler.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputHandler.java new file mode 100755 index 000000000..45add085e --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputHandler.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 SAP AG and others + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lazar Kirchev, SAP AG - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.console.supportability; + +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.InputHandler; + +/** + * 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) { + super(input, in, out); + inputScanner = new ConsoleInputScanner(in, out); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputScanner.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputScanner.java new file mode 100755 index 000000000..e9e47fb17 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/ConsoleInputScanner.java @@ -0,0 +1,502 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.supportability; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.service.command.CommandSession; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.Scanner; +import org.eclipse.equinox.console.common.SimpleByteBuffer; +import org.eclipse.equinox.console.completion.CompletionHandler; +import org.osgi.framework.BundleContext; + +/** + * 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, insert, home, end, pageup, pagedown, tab completion. + */ +public class ConsoleInputScanner extends Scanner { + + private static final byte TAB = 9; + private boolean isCR = false; + private boolean replace = false; + private boolean isCompletionMode = false; + // shows if command history should be saved - it is turned off in cases when passwords are to be entered + private boolean isHistoryEnabled = true; + + private final HistoryHolder history; + private final SimpleByteBuffer buffer; + private CommandSession session; + private BundleContext context; + private Candidates candidates; + private int originalCursorPos; + + public ConsoleInputScanner(ConsoleInputStream toShell, OutputStream toTelnet) { + super(toShell, toTelnet); + history = new HistoryHolder(); + buffer = new SimpleByteBuffer(); + } + + public void toggleHistoryEnabled(boolean isEnabled) { + isHistoryEnabled = isEnabled; + } + + public void setSession(CommandSession session) { + this.session = session; + } + + public void setContext(BundleContext context) { + this.context = context; + } + + public void scan(int b) throws IOException { + b &= 0xFF; + if (isCR) { + isCR = false; + if (b == LF) { + return; + } + } + + if (b != TAB) { + if (isCompletionMode == true) { + isCompletionMode = false; + candidates = null; + originalCursorPos = 0; + } + } + + if (isEsc) { + scanEsc(b); + } else { + if (b == getBackspace()) { + backSpace(); + } else if(b == TAB) { + if (isCompletionMode == false) { + isCompletionMode = true; + processTab(); + } else { + processNextTab(); + } + } 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(); + } + + 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); + } + } + } + + protected void processTab() throws IOException { + CompletionHandler completionHandler = new CompletionHandler(context, session); + Map<String, Integer> completionCandidates = completionHandler.getCandidates(buffer.copyCurrentData(), buffer.getPos()); + + if (completionCandidates.size() == 1) { + completeSingleCandidate(completionCandidates); + isCompletionMode = false; + return; + } + printNewLine(); + if (completionCandidates.size() == 0) { + printCompletionError(); + isCompletionMode = false; + } else { + processCandidates(completionCandidates); + } + printNewLine(); + printPrompt(); + } + + protected void processCandidates(Map<String, Integer> completionCandidates) throws IOException{ + Set<String> candidatesNamesSet = completionCandidates.keySet(); + String[] candidatesNames = (candidatesNamesSet.toArray(new String[0])); + originalCursorPos = buffer.getPos(); + String[] candidateSuffixes = new String[candidatesNames.length]; + for (int i = 0; i < candidatesNames.length; i++) { + String candidateName = candidatesNames[i]; + candidateSuffixes[i] = getCandidateSuffix(candidateName, completionCandidates.get(candidateName), originalCursorPos); + for (byte symbol : candidateName.getBytes()) { + echo(symbol); + } + printNewLine(); + } + + String commonPrefix = getCommonPrefix(candidateSuffixes); + candidates = new Candidates(removeCommonPrefix(candidateSuffixes, commonPrefix)); + printString(commonPrefix, false); + originalCursorPos = buffer.getPos(); + } + + protected void processNextTab() throws IOException { + if (candidates == null) { + return; + } + + while (originalCursorPos < buffer.getPos()) { + backSpace(); + } + + String candidate = candidates.getCurrent(); + if(!candidate.equals("")) { + printString(candidate, true); + } + } + + protected void printCandidate(String candidate, int startIndex, int completionIndex) throws IOException { + String suffix = getCandidateSuffix(candidate, startIndex, completionIndex); + if(suffix.equals("")) { + return; + } + printString(suffix, true); + } + + protected void printString(String st, boolean isEcho) throws IOException { + for (byte symbol : st.getBytes()) { + buffer.insert(symbol); + if (isEcho){ + echo(symbol); + } + } + flush(); + } + + protected String getCommonPrefix(String[] names) { + if (names.length == 0) { + return ""; + } + + if (names.length == 1) { + return names[0]; + } + + StringBuilder builder = new StringBuilder(); + char[] name = names[0].toCharArray(); + for(char c : name) { + String prefix = builder.append(c).toString(); + for (int i = 1; i < names.length; i ++) { + if (!names[i].startsWith(prefix)) { + return prefix.substring(0, prefix.length() - 1); + } + } + } + + return builder.toString(); + } + + protected String[] removeCommonPrefix(String [] names, String commonPrefix){ + ArrayList<String> result = new ArrayList<String>(); + for (String name : names) { + String nameWithoutPrefix = name.substring(commonPrefix.length()); + if (nameWithoutPrefix.length() > 0) { + result.add(nameWithoutPrefix); + } + } + result.add(""); + return result.toArray(new String[0]); + } + + protected String getCandidateSuffix(String candidate, int startIndex, int completionIndex) { + int partialLength = completionIndex - startIndex; + if (partialLength >= candidate.length()) { + return ""; + } + return candidate.substring(partialLength); + } + + protected void completeSingleCandidate(Map<String, Integer> completionCandidates) throws IOException { + Set<String> keys = completionCandidates.keySet(); + String key = (keys.toArray(new String[0]))[0]; + int startIndex = completionCandidates.get(key); + printCandidate(key, startIndex, buffer.getPos()); + } + + protected void printCompletionError() throws IOException { + byte[] curr = buffer.getCurrentData(); + if (isHistoryEnabled == true) { + history.add(curr); + } + + String errorMessage = "No completion available"; + for (byte symbol : errorMessage.getBytes()) { + echo(symbol); + } + } + + protected void printNewLine() throws IOException{ + echo(CR); + echo(LF); + flush(); + } + + protected void printPrompt() throws IOException{ + echo('o'); + echo('s'); + echo('g'); + echo('i'); + echo('>'); + echo(SPACE); + echoBuff(); + flush(); + } + + private void processData() throws IOException { +// buffer.add(CR); + buffer.add(LF); + echo(CR); + echo(LF); + flush(); + byte[] curr = buffer.getCurrentData(); + if (isHistoryEnabled == true) { + history.add(curr); + } + 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(); + } + + private static class Candidates { + private String[] candidates; + private int currentCandidateIndex = 0; + + public Candidates(String[] candidates) { + this.candidates = candidates.clone(); + } + + public String getCurrent() { + if (currentCandidateIndex >= candidates.length) { + currentCandidateIndex = 0; + } + + return candidates[currentCandidateIndex++]; + } + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/HistoryHolder.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/HistoryHolder.java new file mode 100755 index 000000000..52bdd429a --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/supportability/HistoryHolder.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/Callback.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/Callback.java new file mode 100755 index 000000000..2874b3c74 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/Callback.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.telnet; + +public interface Callback { + + public void finished(); +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallback.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallback.java new file mode 100755 index 000000000..c46811816 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/NegotiationFinishedCallback.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.telnet; +/** + * A callback through which the TelnetInputScanner notifies the TelnetConnectionManger + * that the terminal type negotiation with the client has finished. This is importednt, because + * the TelnetConnectionManager should start the CommandSession after the negotiation is finished. + * This is necessary, because the user input should be interpreted with the correct terminal type. + */ +public class NegotiationFinishedCallback implements Callback { + + private TelnetConnection telnetConnection; + + public NegotiationFinishedCallback(TelnetConnection telnetConnection) { + this.telnetConnection = telnetConnection; + } + + public void finished() { + telnetConnection.telnetNegotiationFinished(); + } + +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetCommand.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetCommand.java new file mode 100755 index 000000000..5673a3035 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetCommand.java @@ -0,0 +1,244 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.telnet; + +import java.net.BindException; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.Descriptor; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +/** + * This class implements a command for starting/stopping a simple telnet server. + * + */ +public class TelnetCommand { + + private String defaultHost = null; + private int defaultPort; + private List<CommandProcessor> processors = new ArrayList<CommandProcessor>(); + private final BundleContext context; + private String host = null; + private int port; + private TelnetServer telnetServer = null; + private ServiceRegistration<?> configuratorRegistration; + private boolean isEnabled = false; + + private static final String HOST = "host"; + private static final String PORT = "port"; + private static final String USE_CONFIG_ADMIN_PROP = "osgi.console.useConfigAdmin"; + private static final String TELNET_PID = "osgi.console.telnet"; + private static final String CONSOLE_PROP = "osgi.console"; + private static final String ENABLED = "enabled"; + private final Object lock = new Object(); + + public TelnetCommand(CommandProcessor processor, BundleContext context) + { + processors.add(processor); + this.context = context; + if ("true".equals(context.getProperty(USE_CONFIG_ADMIN_PROP))) { + Dictionary<String, String> telnetProperties = new Hashtable<String, String>(); + telnetProperties.put(Constants.SERVICE_PID, TELNET_PID); + try { + synchronized (lock) { + configuratorRegistration = context.registerService(ManagedService.class.getName(), new TelnetConfigurator(), telnetProperties); + } + } catch (NoClassDefFoundError e) { + System.out.println("Configuration Admin not available!"); + return; + } + } else { + parseHostAndPort(); + } + } + + private void parseHostAndPort() { + String telnetPort = null; + String consolePropValue = context.getProperty(CONSOLE_PROP); + if(consolePropValue != null) { + int index = consolePropValue.lastIndexOf(":"); + if (index > -1) { + defaultHost = consolePropValue.substring(0, index); + } + telnetPort = consolePropValue.substring(index + 1); + isEnabled = true; + } + if (telnetPort != null && !"".equals(telnetPort)) { + try { + defaultPort = Integer.parseInt(telnetPort); + } catch (NumberFormatException e) { + // do nothing + } + } + } + + public synchronized void start() { + Dictionary<String, Object> properties = new Hashtable<String, Object>(); + properties.put("osgi.command.scope", "equinox"); + properties.put("osgi.command.function", new String[] {"telnet"}); + if ((port > 0 || defaultPort > 0) && isEnabled == true) { + try{ + telnet(new String[]{"start"}); + } catch (Exception e) { + System.out.println("Cannot start telnet. Reason: " + e.getMessage()); + e.printStackTrace(); + } + } + context.registerService(TelnetCommand.class.getName(), this, properties); + } + + @Descriptor("start/stop a telnet server") + public synchronized void telnet(String[] arguments) throws Exception + { + String command = null; + int newPort = 0; + String newHost = null; + + for(int i = 0; i < arguments.length; i++) { + if("-?".equals(arguments[i]) || "-help".equals(arguments[i])) { + printHelp(); + return; + } else if("start".equals(arguments[i])) { + command = "start"; + } else if ("stop".equals(arguments[i])) { + command = "stop"; + } else if ("-port".equals(arguments[i]) && (arguments.length > i + 1)) { + i++; + newPort = Integer.parseInt(arguments[i]); + } else if ("-host".equals(arguments[i]) && (arguments.length > i + 1)) { + i++; + newHost = arguments[i]; + } else { + throw new Exception("Unrecognized telnet command/option " + arguments[i]); + } + } + + if (command == null) { + throw new Exception("No telnet command specified"); + } + + if (newPort != 0) { + port = newPort; + } else if (port == 0) { + port = defaultPort; + } + + if (port == 0) { + throw new Exception("No telnet port specified"); + } + + if (newHost != null) { + host = newHost; + } else { + host = defaultHost; + } + + if ("start".equals(command)) { + if (telnetServer != null) { + throw new IllegalStateException("telnet is already running on port " + port); + } + + try { + telnetServer = new TelnetServer(context, processors, host, port); + } catch (BindException e) { + throw new Exception("Port " + port + " already in use"); + } + + telnetServer.setName("equinox telnet"); + telnetServer.start(); + } else if ("stop".equals(command)) { + if (telnetServer == null) { + throw new IllegalStateException("telnet is not running."); + } + + telnetServer.stopTelnetServer(); + telnetServer = null; + } + } + + public synchronized void addCommandProcessor(CommandProcessor processor) { + processors.add(processor); + telnetServer.addCommandProcessor(processor); + } + + public synchronized void removeCommandProcessor(CommandProcessor processor) { + processors.remove(processor); + telnetServer.removeCommandProcessor(processor); + } + + private void printHelp() { + StringBuffer help = new StringBuffer(); + help.append("telnet - start simple telnet server"); + help.append("\n"); + help.append("Usage: telnet start | stop [-port port] [-host host]"); + help.append("\n"); + help.append("\t"); + help.append("-port"); + help.append("\t"); + help.append("listen port (default="); + help.append(defaultPort); + help.append(")"); + help.append("\n"); + help.append("\t"); + help.append("-host"); + help.append("\t"); + help.append("local host address to listen on (default is none - listen on all network interfaces)"); + help.append("\n"); + help.append("\t"); + help.append("-?, -help"); + help.append("\t"); + help.append("show help"); + System.out.println(help.toString()); + } + + class TelnetConfigurator implements ManagedService { + @SuppressWarnings("rawtypes") + private Dictionary properties; + @SuppressWarnings({ "rawtypes", "unchecked" }) + public synchronized void updated(Dictionary props) throws ConfigurationException { + if (props != null) { + this.properties = props; + properties.put(Constants.SERVICE_PID, TELNET_PID); + } else { + return; + } + + defaultPort = Integer.parseInt(((String)properties.get(PORT))); + defaultHost = (String)properties.get(HOST); + if (properties.get(ENABLED) == null) { + isEnabled = false; + } else { + isEnabled = Boolean.parseBoolean((String)properties.get(ENABLED)); + } + synchronized (lock) { + configuratorRegistration.setProperties(properties); + } + if (telnetServer == null && isEnabled == true) { + try { + telnet(new String[]{"start"}); + } catch (Exception e) { + System.out.println("Cannot start telnet: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetConnection.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetConnection.java new file mode 100755 index 000000000..56802cf6e --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetConnection.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 SAP AG and others + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lazar Kirchev, SAP AG - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.console.telnet; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.Socket; +import org.apache.felix.service.command.CommandProcessor; +import org.apache.felix.service.command.CommandSession; +import java.io.Closeable; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.supportability.ConsoleInputHandler; +import org.eclipse.equinox.console.supportability.ConsoleInputScanner; +import org.osgi.framework.BundleContext; + +/** + * This class manages a telnet connection. It is responsible for wrapping the original io streams + * from the socket, and starting a CommandSession to execute commands from the telnet. + * + */ +public class TelnetConnection extends Thread implements Closeable { + + private Socket socket; + private CommandProcessor processor; + private BundleContext context; + protected boolean isTelnetNegotiationFinished = false; + private Callback callback; + private static final long TIMEOUT = 1000; + private static final long NEGOTIATION_TIMEOUT = 60000; + private static final String PROMPT = "prompt"; + private static final String OSGI_PROMPT = "osgi> "; + private static final String SCOPE = "SCOPE"; + private static final String EQUINOX_SCOPE = "equinox:*"; + private static final String CLOSEABLE = "CLOSEABLE"; + + public TelnetConnection (Socket socket, CommandProcessor processor, BundleContext context) { + this.socket = socket; + this.processor = processor; + this.context = context; + callback = new NegotiationFinishedCallback(this); + } + + public void run() { + try { + ConsoleInputStream in = new ConsoleInputStream(); + TelnetOutputStream out = new TelnetOutputStream(socket.getOutputStream()); + out.autoSend(); + TelnetInputHandler telnetInputHandler = new TelnetInputHandler(socket.getInputStream(), in, out, callback); + telnetInputHandler.start(); + + long start = System.currentTimeMillis(); + + synchronized (this) { + while (isTelnetNegotiationFinished == false && System.currentTimeMillis() - start < NEGOTIATION_TIMEOUT) { + try { + wait(TIMEOUT); + } catch (InterruptedException e) { + // do nothing + } + } + } + final CommandSession session; + PrintStream output = new PrintStream(out); + + ConsoleInputStream inp = new ConsoleInputStream(); + + ConsoleInputHandler consoleInputHandler = new ConsoleInputHandler(in, inp, out); + consoleInputHandler.getScanner().setBackspace(telnetInputHandler.getScanner().getBackspace()); + consoleInputHandler.getScanner().setDel(telnetInputHandler.getScanner().getDel()); + consoleInputHandler.getScanner().setCurrentEscapesToKey(telnetInputHandler.getScanner().getCurrentEscapesToKey()); + consoleInputHandler.getScanner().setEscapes(telnetInputHandler.getScanner().getEscapes()); + ((ConsoleInputScanner)consoleInputHandler.getScanner()).setContext(context); + + consoleInputHandler.start(); + + session = processor.createSession(inp, output, output); + session.put(SCOPE, EQUINOX_SCOPE); + session.put(PROMPT, OSGI_PROMPT); + // Store this closeable object in the session, so that the disconnect command can close it + session.put(CLOSEABLE, this); + ((ConsoleInputScanner)consoleInputHandler.getScanner()).setSession(session); + + try { + session.execute("gosh --login --noshutdown"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + session.close(); + try { + socket.close(); + } + catch (IOException e) { + // do nothing + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void close() { + try { + this.interrupt(); + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public synchronized void telnetNegotiationFinished() { + isTelnetNegotiationFinished = true; + notify(); + } +} diff --git a/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputHandler.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputHandler.java new file mode 100755 index 000000000..0be072d53 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputHandler.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 SAP AG and others + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lazar Kirchev, SAP AG - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.console.telnet; + +import java.io.InputStream; +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.InputHandler; + +/** + * 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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputScanner.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputScanner.java new file mode 100755 index 000000000..57f9af307 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetInputScanner.java @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 SAP AG and others + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lazar Kirchev, SAP AG - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.console.telnet; + +import org.eclipse.equinox.console.common.ConsoleInputStream; +import org.eclipse.equinox.console.common.ConsoleOutputStream; +import org.eclipse.equinox.console.common.KEYS; +import org.eclipse.equinox.console.common.Scanner; +import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings; + +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. Also, it performs + * terminal type negotiation with the telnet client. This is important for some of the escape sequences, + * which are different for the different terminal types. Without such negotiation, + * some keys (such as backspace, del, insert, home, etc.) may not be correctly + * interpreted by the telnet server. Currently the supported terminal types are + * ANSI, VT100, VT220, VT320, XTERM and SCO. The support is limited to the following + * keys - BACKSPACE, DEL, INSERT, HOME, END, PAGEUP, PAGEDOWN, ARROWS. + */ +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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetOutputStream.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetOutputStream.java new file mode 100755 index 000000000..4d46494c2 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetOutputStream.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010, 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 API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.console.telnet; + +import org.eclipse.equinox.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/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetServer.java b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetServer.java new file mode 100755 index 000000000..3ea618918 --- /dev/null +++ b/bundles/org.eclipse.equinox.console/src/org/eclipse/equinox/console/telnet/TelnetServer.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2010, 2011 SAP AG and others + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Lazar Kirchev, SAP AG - initial API and implementation + * IBM Corporation - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.console.telnet; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.service.command.CommandProcessor; +import org.osgi.framework.BundleContext; + +/** + * A telnet server, which listens for telnet connections and starts a telnet connection manager + * when a connection is accepted. If there are multiple CommandProcessor, a telnet connection + * is created for each of them. + * + */ +public class TelnetServer extends Thread { + + private ServerSocket server; + private boolean isRunning = true; + private List<CommandProcessor> processors = null; + private BundleContext context; + private List<Socket> sockets = new ArrayList<Socket>(); + private Map<CommandProcessor, List<TelnetConnection>> processorToConnectionsMapping = new HashMap<CommandProcessor, List<TelnetConnection>>(); + + public TelnetServer(BundleContext context, List<CommandProcessor> processors, String host, int port) throws IOException { + this.context = context; + this.processors = processors; + if(host != null) { + server = new ServerSocket(port, 0, InetAddress.getByName(host)); + } else { + server = new ServerSocket(port); + } + } + + public void run() + { + try + { + while (isRunning) + { + final Socket socket = server.accept(); + sockets.add(socket); + for (CommandProcessor processor : processors) { + TelnetConnection telnetConnection = new TelnetConnection(socket, processor, context); + List<TelnetConnection> telnetConnections = processorToConnectionsMapping.get(processor); + if (telnetConnections == null) { + telnetConnections = new ArrayList<TelnetConnection>(); + processorToConnectionsMapping.put(processor, telnetConnections); + } + telnetConnections.add(telnetConnection); + telnetConnection.start(); + } + } + } catch (IOException e) { + if (isRunning == true) { + e.printStackTrace(); + } + } finally { + isRunning = false; + try { + if (server != null) { + server.close(); + } + } catch (IOException e){ + // do nothing + } + } + } + + public synchronized void addCommandProcessor(CommandProcessor processor) { + List<TelnetConnection> telnetConnections = new ArrayList<TelnetConnection>(); + for (Socket socket : sockets) { + TelnetConnection telnetConnection = new TelnetConnection(socket, processor, context); + telnetConnections.add(telnetConnection); + telnetConnection.start(); + } + processorToConnectionsMapping.put(processor, telnetConnections); + } + + public synchronized void removeCommandProcessor(CommandProcessor processor) { + List<TelnetConnection> telnetConnections = processorToConnectionsMapping.remove(processor); + if (telnetConnections != null) { + for (TelnetConnection telnetConnection : telnetConnections) { + telnetConnection.close(); + } + } + } + + public synchronized void stopTelnetServer() { + isRunning = false; + try { + if (server != null) { + server.close(); + } + } catch (IOException e){ + // do nothing + } + + for(List<TelnetConnection> telnetConnections : processorToConnectionsMapping.values()) { + for (TelnetConnection telnetConnection : telnetConnections) { + telnetConnection.close(); + } + } + + this.interrupt(); + } +} diff --git a/bundles/org.eclipse.equinox.slf4j.stub/.classpath b/bundles/org.eclipse.equinox.slf4j.stub/.classpath new file mode 100755 index 000000000..c83506765 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/.classpath @@ -0,0 +1,6 @@ +<?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/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.slf4j.stub/.project b/bundles/org.eclipse.equinox.slf4j.stub/.project new file mode 100755 index 000000000..cac65e519 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.equinox.slf4j.stub</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/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 000000000..4f0ba5ed7 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Tue Jun 07 08:08:08 CDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 000000000..9b902ed4e --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Tue Jun 07 08:08:08 CDT 2011 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.eclipse.equinox.slf4j.stub/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.slf4j.stub/META-INF/MANIFEST.MF new file mode 100755 index 000000000..7ab97499a --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/META-INF/MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: slf4j Stub +Bundle-SymbolicName: org.eclipse.equinox.slf4j.stub +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Provide-Capability: generic; generic="org.slf4j.impl.StaticLoggerBinder"; version:Version="1.6.1" diff --git a/bundles/org.eclipse.equinox.slf4j.stub/META-INF/p2.inf b/bundles/org.eclipse.equinox.slf4j.stub/META-INF/p2.inf new file mode 100755 index 000000000..b0b7a6cb9 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/META-INF/p2.inf @@ -0,0 +1,9 @@ +# this file is a workaround for bug 313553 +# https://bugs.eclipse.org/bugs/show_bug.cgi?id=313553 +# +# see also +# https://bugs.eclipse.org/bugs/show_bug.cgi?id=177851#c9 + +provides.0.namespace = org.slf4j.api +provides.0.name = org.slf4j.impl.StaticLoggerBinder +provides.0.version = 1.6.1 diff --git a/bundles/org.eclipse.equinox.slf4j.stub/about.html b/bundles/org.eclipse.equinox.slf4j.stub/about.html new file mode 100755 index 000000000..460233046 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/about.html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> +<title>About</title> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>June 2, 2006</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.slf4j.stub/build.properties b/bundles/org.eclipse.equinox.slf4j.stub/build.properties new file mode 100755 index 000000000..401b13f73 --- /dev/null +++ b/bundles/org.eclipse.equinox.slf4j.stub/build.properties @@ -0,0 +1,14 @@ +############################################################################### +# Copyright (c) 2011 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### +bin.includes = META-INF/,\ + .,\ + about.html +src.includes = about.html |