/******************************************************************************* * Copyright (c) 2014 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 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation *******************************************************************************/ package org.eclipse.concierge.shell; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.framework.startlevel.BundleStartLevel; import org.eclipse.concierge.shell.commands.ShellCommandGroup; /** * <p> * The shell class. Implements a simple shell for executing framework-related * commands. * </p> * <p> * Other bundle can register services of the type * <code>org.eclipse.concierge.shell.commands.ShellCommandGroup</code> and * dynamically extend the shell by providing their own commands. * </p> * * @author Jan S. Rellermeyer, IBM Research */ public class Shell extends Thread implements ServiceListener { /** * is the shell running ? */ static boolean running; /** * the known command groups. */ private Map<String, ShellCommandGroup> commandGroups = new HashMap<String, ShellCommandGroup>(1); /** * empty string array. */ private static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * the standard out stream. */ static PrintStream out; /** * the standard err stream. */ static PrintStream err; /** * the default commands. */ private DefaultCommands defaultCommands; /** * Create a new shell instance. * * @param out * the standard out. * @param err * the standard err. * @param groups * all ShellCommandGroup plugins. */ public Shell(final PrintStream out, final PrintStream err, final ShellCommandGroup[] groups) { Shell.out = out; Shell.err = err; defaultCommands = new DefaultCommands(); Shell.running = true; for (int i = 0; i < groups.length; i++) { commandGroups.put(groups[i].getGroup(), groups[i]); } start(); } /** * main thread loop. */ public void run() { final BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { while (running) { out.print("\r\nConcierge> "); String s = in.readLine(); if (s == null) { running = false; } if (running) { handleCommand(s); } } } catch (IOException e) { e.printStackTrace(err); } } /** * handle a command. * * @param s * the command string */ private void handleCommand(final String s) { try { int pos; if (s.equals("")) { return; } final String grcmd = (pos = s.indexOf(" ")) > -1 ? s.substring(0, pos).toLowerCase() : s.toLowerCase(); final String[] args = pos > -1 ? getArgs(s.substring(pos + 1)) : EMPTY_STRING_ARRAY; final String group = (pos = grcmd.indexOf(".")) > -1 ? grcmd.substring(0, pos) : ""; final String command = pos > -1 ? grcmd.substring(pos + 1) : grcmd; if (command.equals("help")) { out.println(defaultCommands.getHelp()); ShellCommandGroup[] groups = (ShellCommandGroup[]) commandGroups.values() .toArray(new ShellCommandGroup[commandGroups.size()]); for (int i = 0; i < groups.length; i++) { out.println(groups[i].getHelp()); } return; } if (group.equals("")) { defaultCommands.handleCommand(command, args); } else { ShellCommandGroup commandGroup = (ShellCommandGroup) commandGroups.get(group); if (commandGroup != null) { commandGroup.handleCommand(command, args); } else { out.println("unknown comand group " + group); } } } catch (final Throwable t) { t.printStackTrace(err); } } /** * get the arguments of a command. * * @param args * the argument string. * @return the arguments. */ protected String[] getArgs(final String args) { final StringTokenizer tokenizer = new StringTokenizer(args, " "); final ArrayList<String> result = new ArrayList<String>(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken().trim(); if (token.startsWith("\"")) { StringBuffer buffer = new StringBuffer(); buffer.append(token.substring(1)); while (!token.endsWith("\"")) { if (!tokenizer.hasMoreTokens()) { throw new RuntimeException("Expression not well-formed."); } token = tokenizer.nextToken(); buffer.append(' '); buffer.append(token); } token = buffer.substring(0, buffer.length() - 1); } result.add(token); } return result.toArray(new String[result.size()]); } /** * get a bundle. * * @param bundleIdString * the bundle id as string. * @return the bundle. */ protected static Bundle getBundle(final String bundleIdString) { int pos; final long bundleId = Long.parseLong( (pos = bundleIdString.indexOf(" ")) > -1 ? bundleIdString.substring(0, pos) : bundleIdString); final Bundle bundle = ShellActivator.context.getBundle(bundleId); if (bundle == null) { err.println("Unknown bundle " + bundleId); } return bundle; } protected static ServiceReference<?> getServiceRef(final String serviceIdString) { try { final ServiceReference<?>[] ref = ShellActivator.context.getServiceReferences((String) null, "(" + Constants.SERVICE_ID + "=" + serviceIdString + ")"); if (ref == null) { err.println("Unknown service " + serviceIdString); return null; } return ref[0]; } catch (final InvalidSyntaxException e) { e.printStackTrace(err); return null; } } /** * the default commands. * * @author Jan S. Rellermeyer */ protected static final class DefaultCommands implements ShellCommandGroup { /** * @return the group identifier. It is the empty string for the default * command group. * @see org.eclipse.concierge.shell.commands.ShellCommandGroup#getGroup() */ public String getGroup() { return ""; } /** * @return the help page. * @see org.eclipse.concierge.shell.commands.ShellCommandGroup#getHelp() */ public String getHelp() { return "Concierge Shell:\n\tbundles\n\tservices [<bundle>]\n\tinstall <URL of bundle>\n\tstart <bundleId>\n\tstop <bundleId>\n\tupdate <bundleId> [<URL>]\n\tuninstall <bundleId>\n\theaders <bundleId>\n\tstartlevel <bundleId> [startlevel]\n\tproperties <serviceId>\n\tprintenv \n\trestart (framework)\n\texit (framework)\n\tquit (shell)\n"; } /** * handle a command. * * @param command * the command. * @param args * the arguments. * @see org.eclipse.concierge.shell.commands.ShellCommandGroup#handleCommand(java.lang.String, * java.lang.String[]) */ public void handleCommand(final String command, final String[] args) { try { if ("bundles".equals(command)) { out.println("Bundles:"); final StringBuffer buffer = new StringBuffer(); final Bundle[] bundles = ShellActivator.context.getBundles(); for (int i = 0; i < bundles.length; i++) { buffer.append("["); buffer.append(formatId(bundles[i].getBundleId())); buffer.append("] "); buffer.append(status(bundles[i].getState())); buffer.append(" "); buffer.append(nameOrLocation(bundles[i])); buffer.append("\r\n"); } out.println(buffer.toString()); return; } else if ("services".equals(command)) { out.println("Services:"); final Bundle[] bundles = ShellActivator.context.getBundles(); final StringBuffer buffer = new StringBuffer(); if (args.length == 0) { for (int i = 0; i < bundles.length; i++) { final ServiceReference<?>[] refs = bundles[i].getRegisteredServices(); if (refs != null && refs.length > 0) { buffer.append(bundles[i] + "\n"); for (int j = 0; j < refs.length; j++) { buffer.append("\t[Service "); buffer.append(refs[j].getProperty(Constants.SERVICE_ID)); buffer.append("] "); buffer.append(Arrays.asList((Object[]) refs[j].getProperty(Constants.OBJECTCLASS))); buffer.append('\n'); } } } out.println(buffer.toString()); } else { final Bundle bundle = getBundle(args[0]); if (bundle == null) { return; } final ServiceReference<?>[] refs = bundle.getRegisteredServices(); buffer.append(bundle + "\n"); if (refs != null && refs.length > 0) { for (int i = 0; i < refs.length; i++) { buffer.append("\t[Service "); buffer.append(refs[i].getProperty(Constants.SERVICE_ID)); buffer.append("] "); buffer.append(Arrays.asList((Object[]) refs[i].getProperty(Constants.OBJECTCLASS))); buffer.append("\n"); } } else { buffer.append("\t<none>\n"); } out.println(buffer.toString()); } return; } else if ("properties".equals(command)) { if (args.length > 0) { final ServiceReference<?> ref = getServiceRef(args[0]); if (ref == null) { return; } final String[] keys = ref.getPropertyKeys(); out.println("Service [" + args[0] + "]:"); for (int i = 0; i < keys.length; i++) { Object value = ref.getProperty(keys[i]); if (value.getClass().isArray()) { value = Arrays.asList((Object[]) value); } out.println("\t'" + keys[i] + "' = '" + value + "'"); } } else { err.println("Missing argument <serviceId>"); } } else if ("filter".equals(command)) { if (args.length == 1) { final ServiceReference<?>[] refs = ShellActivator.context.getServiceReferences((String) null, args[0]); if (refs != null && refs.length > 0) { for (int j = 0; j < refs.length; j++) { out.println("\t[" + refs[j].getProperty(Constants.SERVICE_ID) + "] " + Arrays.asList((Object[]) refs[j].getProperty(Constants.OBJECTCLASS))); } } } else { out.println("Usage: filter <filterExpression>"); } } else if ("install".equals(command)) { if (args.length > 0) { try { ShellActivator.context.installBundle(args[0]); } catch (final Exception e) { err.println("Invalid bundle location or bundle content " + args[0]); } } else { err.println("Missing argument <bundleURL>"); } return; } else if ("start".equals(command)) { final Bundle bundle; if (args.length > 0) { if ((bundle = getBundle(args[0])) != null) { try { bundle.start(); } catch (final BundleException be) { be.printStackTrace(); final Throwable t = be.getNestedException(); if (t != null) { System.err.println("Nested exception:"); t.printStackTrace(); } return; } out.println(bundle.toString() + " started."); } } else { err.println("Missing argument <bundleId>"); } return; } else if ("stop".equals(command)) { final Bundle bundle; if (args.length > 0) { if ((bundle = getBundle(args[0])) != null) { bundle.stop(); System.out.println(bundle.toString() + " stopped."); } } else { err.println("Missing argument <bundleId>"); } return; } else if ("uninstall".equals(command)) { final Bundle bundle; if (args.length > 0) { if ((bundle = getBundle(args[0])) != null) { bundle.uninstall(); out.println(bundle.toString() + " uninstalled."); } } else { System.err.println("Missing argument <bundleId>"); } return; } else if ("update".equals(command)) { final Bundle bundle; if (args.length == 1) { if ((bundle = getBundle(args[0])) != null) { try { bundle.update(); } catch (final Exception e) { err.println("Update failed:"); err.println(e.getMessage()); if (e.getCause() != null) { err.println(e.getCause().getMessage()); } return; } out.println(bundle.toString() + " updated."); } } else if (args.length > 1) { if ((bundle = getBundle(args[0])) != null) { bundle.update(new URL(args[1]).openStream()); out.println(bundle.toString() + " updated."); } } else { err.println("Missing argument <bundleId>"); } return; } else if ("headers".equals(command)) { final Bundle bundle; if (args.length > 0) { if ((bundle = getBundle(args[0])) != null) { final Dictionary<String, String> dict = bundle.getHeaders(); final StringBuffer buffer = new StringBuffer(); buffer.append("Headers for " + bundle + ":\n"); for (Enumeration<String> en = dict.keys(); en.hasMoreElements();) { final Object key = en.nextElement(); buffer.append("\t" + key + " = " + dict.get(key) + "\n"); } out.println(buffer.toString()); } } else { err.println("Missing argument <bundleId>"); } return; } else if ("startlevel".equals(command)) { if (args.length > 0) { final Bundle bundle; if ((bundle = getBundle(args[0])) != null) { final BundleStartLevel bsl = bundle.adapt(BundleStartLevel.class); if (args.length > 1) { try { bsl.setStartLevel(Integer.parseInt(args[1])); } catch (final IllegalArgumentException e) { err.println("Invalid start level " + args[1]); return; } } final StringBuffer buffer = new StringBuffer(); buffer.append("Startlevel for " + bundle + ": "); buffer.append(bsl.getStartLevel()); out.println(buffer.toString()); } } else { err.println("Missing argument <bundleId>"); } return; } else if ("restart".equals(command)) { try { ShellActivator.context.getBundle(0).update(); } catch (BundleException e) { e.printStackTrace(); } } else if ("quit".equals(command)) { try { ShellActivator.context.getBundle().stop(); } catch (BundleException e) { e.printStackTrace(); } return; } else if ("exit".equals(command)) { try { ShellActivator.context.getBundle(0).stop(); } catch (BundleException e) { e.printStackTrace(); } running = false; return; } else if ("printenv".equals(command)) { final String[] keys = (String[]) System.getProperties().keySet() .toArray(new String[System.getProperties().size()]); for (int i = 0; i < keys.length; i++) { final String val = System.getProperty(keys[i]); out.println(keys[i] + " = " + val); } } else { out.println("unknown command " + command); } } catch (final BundleException be) { be.printStackTrace(err); } catch (final NumberFormatException nfe) { err.println("Illegal argument " + args[0]); } catch (final MalformedURLException e) { err.println("Malformed URL " + args[1]); } catch (final IOException ioe) { ioe.printStackTrace(err); } catch (final InvalidSyntaxException e) { e.printStackTrace(err); } } /** * Format the bundle id. * * @param id * @return */ private String formatId(final long id) { return (id < 10) ? " " + id : "" + id; } private String nameOrLocation(final Bundle b) { final Object name = b.getHeaders().get("Bundle-Name"); return name != null ? name.toString() : b.getLocation(); } /** * get the status. * * @param state * the state code. * @return the status message. */ private String status(final int state) { switch (state) { case Bundle.ACTIVE: return "(active)"; case Bundle.INSTALLED: return "(installed)"; case Bundle.RESOLVED: return ("(resolved)"); case Bundle.STARTING: return ("(starting) "); case Bundle.STOPPING: return ("(stopping)"); case Bundle.UNINSTALLED: return ("(uninstalled)"); } return null; } } /** * service listener method. * * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent) */ public void serviceChanged(final ServiceEvent event) { final ServiceReference<?> ref = event.getServiceReference(); final int type = event.getType(); if (type == ServiceEvent.REGISTERED) { final ShellCommandGroup group = (ShellCommandGroup) ShellActivator.context.getService(ref); commandGroups.put(group.getGroup(), group); return; } else if (type == ServiceEvent.UNREGISTERING) { final ShellCommandGroup group = (ShellCommandGroup) ShellActivator.context.getService(ref); commandGroups.remove(group.getGroup()); return; } } }