changeset 563:51f768003f10

Fix regression in ServiceCommand shutdown (PR1134) Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-August/002879.html Reviewed-by: omajid PR1134
author Jon VanAlten <jon.vanalten@redhat.com>
date Wed, 22 Aug 2012 16:28:44 -0400
parents faa61b45ba32
children 23f2ec2942c6
files agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java client/core/src/main/java/com/redhat/thermostat/client/internal/GUIClientCommand.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindObjectsCommandTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindRootCommandTest.java client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/ObjectInfoCommandTest.java common/core/src/main/java/com/redhat/thermostat/common/cli/Command.java common/core/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java common/core/src/main/java/com/redhat/thermostat/common/cli/SimpleCommand.java common/core/src/main/java/com/redhat/thermostat/common/tools/BasicCommand.java common/core/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java common/core/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java common/test/src/main/java/com/redhat/thermostat/test/cli/TestCommand.java launcher/src/main/java/com/redhat/thermostat/launcher/Launcher.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java
diffstat 16 files changed, 118 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -36,173 +36,83 @@
 
 package com.redhat.thermostat.agent.cli;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
+import java.util.concurrent.Semaphore;
 
 import com.redhat.thermostat.agent.cli.db.StorageAlreadyRunningException;
 import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
 import com.redhat.thermostat.agent.cli.impl.locale.Translate;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
-import com.redhat.thermostat.common.NotImplementedException;
 import com.redhat.thermostat.common.cli.ArgumentSpec;
 import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandContextImpl;
 import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.cli.OSGiContext;
 import com.redhat.thermostat.common.cli.SimpleArgumentSpec;
-import com.redhat.thermostat.common.cli.SimpleArguments;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.config.StartupConfiguration;
-import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.cli.SimpleCommand;
 import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.tools.BasicCommand;
+import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.Launcher;
 
 /**
  * Simple service that allows starting Agent and DB Backend
  * in a single step.
  */
-public class ServiceCommand extends BasicCommand implements ActionListener<ApplicationState>, OSGiContext {
+public class ServiceCommand extends SimpleCommand implements ActionListener<ApplicationState> {
     
     private static final String NAME = "service";
-
     private static final String DESCRIPTION = Translate.localize(LocaleResources.COMMAND_SERVICE_DESCRIPTION);
-
     private static final String USAGE = DESCRIPTION;
 
-    private BasicCommand database;
-    private AgentApplication agent;
-    
-    private ActionNotifier<ApplicationState> notifier;
+    private List<ActionListener<ApplicationState>> listeners;
 
-    private CommandContext context;
-    private BundleContext bundleContext;
-    
-    private List<Runnable> tasksOnStop = new CopyOnWriteArrayList<>();
+    private Semaphore agentBarrier = new Semaphore(0);
 
     public ServiceCommand() {
-        database = new StorageCommand();
-        agent = new AgentApplication();
-        notifier = new ActionNotifier<>(this);
-    }
-    
-    @Override
-    public void setBundleContext(BundleContext context) {
-        this.bundleContext = context;
-    }
-    
-    private void addListeners() throws InvalidConfigurationException {
-        // Currently, all the arguments are for the db. We only configure the
-        // agent accordingly to the database settings.
-        // so nothing else is done here at this stage
-        database.getNotifier().addActionListener(this);
-        agent.getNotifier().addActionListener(this);
+        listeners = new ArrayList<>();
+        listeners.add(this);
     }
 
     @Override
     public void run(CommandContext ctx) throws CommandException {
-        context = ctx;
-        try {
-            addListeners();
-            // just run the database, if the database is successful, let the
-            // listeners start the agent for us.
-            database.run(ctx);
-        } catch (InvalidConfigurationException e) {
-            throw new CommandException(e);
-        }
-    }
-
-    @Override
-    public ActionNotifier<ApplicationState> getNotifier() {
-        return notifier;
-    }
-
-    @Override
-    public StartupConfiguration getConfiguration() {
-        throw new NotImplementedException("NYI");
+        Launcher launcher = getLauncher();
+        String[] storageStartArgs = new String[] { "storage", "--start" };
+        launcher.setArgs(storageStartArgs);
+        launcher.run(listeners);
+        agentBarrier.acquireUninterruptibly();
+        String[] storageStopArgs = new String[] { "storage", "--stop" };
+        launcher.setArgs(storageStopArgs);
+        launcher.run();
     }
 
     @Override
     public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-        if (actionEvent.getSource().equals(database)) {
+        if (actionEvent.getSource() instanceof StorageCommand) {
+            StorageCommand storage = (StorageCommand) actionEvent.getSource();
+            // Implementation detail: there is a single StorageCommand instance registered
+            // as an OSGi service.  We remove ourselves as listener so that we don't get
+            // notified in the case that the command is invoked by some other means later.
+            storage.getNotifier().removeActionListener(this);
             switch (actionEvent.getActionId()) {
-            // we are only interested in starting the agent if
-            // we started the database ourselves
             case START:
-                
-                // set a bundle-stop hook if the db was started by us
-                tasksOnStop.add(new Runnable() {
-                    @Override
-                    public void run() {
-                        String[] args = new String[] { "storage", "--stop" };
-                        
-                        ServiceReference launcherRef = bundleContext.getServiceReference(Launcher.class.getName());
-                        if (launcherRef != null) {
-                            Launcher launcher = (Launcher) bundleContext.getService(launcherRef);
-                            launcher.setArgs(args);
-                            launcher.run();
-                        } else {
-                            try {
-                                SimpleArguments arguments = new SimpleArguments();
-                                arguments.addArgument("stop", args[1]);
-                                database.run(new CommandContextImpl(arguments,
-                                        ServiceCommand.this.context.getCommandContextFactory()));
-                                
-                            } catch (CommandException e) {
-                                Logger.getLogger(getClass().getSimpleName()).log(Level.WARNING, "Can't stop database", e);
-                            }
-                        }
-                        
-                    }
-                });
-                
-                String dbUrl = database.getConfiguration().getDBConnectionString();
-                SimpleArguments args = new SimpleArguments();
-                args.addArgument("dbUrl", dbUrl);
-                try {
-                    System.err.println(Translate.localize(LocaleResources.STARTING_AGENT));
-                    agent.run(new CommandContextImpl(args, context.getCommandContextFactory()));
-                } catch (CommandException e) {
-                    notifier.fireAction(ApplicationState.FAIL);
-                }
+                String dbUrl = storage.getConfiguration().getDBConnectionString();
+                Launcher launcher = getLauncher();
+                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
+                System.err.println(Translate.localize(LocaleResources.STARTING_AGENT));
+                launcher.setArgs(agentArgs);
+                launcher.run();
+                agentBarrier.release();
                 break;
-
             case FAIL:
                 System.err.println(Translate.localize(LocaleResources.ERROR_STARTING_DB));
                 Object payload = actionEvent.getPayload();
-                if (payload instanceof ApplicationException) {
-                    ApplicationException exception = (ApplicationException) payload;
-                    if (exception instanceof StorageAlreadyRunningException) {
-                        System.err.println(Translate.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
-                    }
+                if (payload instanceof StorageAlreadyRunningException) {
+                    System.err.println(Translate.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
                 }
-
-                notifier.fireAction(ApplicationState.FAIL);
                 break;
             }
-        } else if (actionEvent.getSource().equals(agent)) {
-            // agent is running
-            switch (actionEvent.getActionId()) {
-            case START:
-                notifier.fireAction(ApplicationState.SUCCESS);
-                break;
-            }
-        }
-    }
-
-    @Override
-    public void disable() {
-        for (Runnable task: tasksOnStop) {
-            task.run();
         }
     }
 
@@ -222,11 +132,25 @@
     }
 
     @Override
+    public boolean isAvailableInShell() {
+        return false;
+    }
+
+    @Override
     public Collection<ArgumentSpec> getAcceptedArguments() {
         ArgumentSpec start = new SimpleArgumentSpec("start", Translate.localize(LocaleResources.COMMAND_SERVICE_ARGUMENT_START_DESCRIPTION));
         ArgumentSpec stop = new SimpleArgumentSpec("stop", Translate.localize(LocaleResources.COMMAND_SERVICE_ARGUMENT_STOP_DESCRIPTION));
         return Arrays.asList(start, stop);
     }
 
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+    private Launcher getLauncher() {
+        OSGIUtils osgi = OSGIUtils.getInstance();
+        return osgi.getService(Launcher.class);
+    }
 
 }
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -104,7 +104,6 @@
         if (parser.isDryRun()) return;
         
         runner = createRunner();
-        
         try {
             switch (parser.getAction()) {
             case START:
@@ -117,7 +116,6 @@
                 break;
             }
             getNotifier().fireAction(ApplicationState.SUCCESS);
-            
         } catch (Exception e) {
             getNotifier().fireAction(ApplicationState.FAIL, e);
         }
@@ -182,11 +180,6 @@
     }
 
     @Override
-    public void disable() {
-        /* NO-OP */
-    }
-
-    @Override
     public DBStartupConfiguration getConfiguration() {
         return configuration;
     }
--- a/client/core/src/main/java/com/redhat/thermostat/client/internal/GUIClientCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/internal/GUIClientCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -83,11 +83,6 @@
     }
 
     @Override
-    public void disable() {
-        // TODO clientMain.shutdown();
-    }
-
-    @Override
     public String getName() {
         return "gui";
     }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindObjectsCommandTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindObjectsCommandTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -232,9 +232,4 @@
 
         assertEquals("Heap ID not found: " + INVALID_HEAP_ID + "\n", factory.getOutput());
     }
-
-    @Test
-    public void testDisable() {
-        cmd.disable(); // No side-effects. Hopefully...
-    }
 }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindRootCommandTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/FindRootCommandTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -324,10 +324,4 @@
             assertEquals("Object not found: fluff", ex.getMessage());
          }
     }
-
-    @Test
-    public void testDisable() {
-        cmd.disable(); // No side effects... hopefully :-)
-    }
-
 }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/ObjectInfoCommandTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/ObjectInfoCommandTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -181,11 +181,6 @@
     }
 
     @Test
-    public void testDisable() {
-        cmd.disable(); // No side effects... hopefully :-)
-    }
-
-    @Test
     public void testSimpleObject() throws CommandException {
         TestCommandContextFactory factory = new TestCommandContextFactory();
         SimpleArguments args = new SimpleArguments();
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/Command.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/Command.java	Wed Aug 22 16:28:44 2012 -0400
@@ -61,23 +61,6 @@
     public void run(CommandContext ctx) throws CommandException;
 
     /**
-     * Called when the command is first installed into the system. This is a
-     * good point to initiate one-time initialization actions.
-     * <p>
-     * Should be idempotent.
-     */
-    public void enable();
-
-    /**
-     * Called when the command is being removed from the system. The command
-     * should cancel any long-term action it has taken, such as any background
-     * tasks or threads it has spawned.
-     * <p>
-     * Should be idempotent.
-     */
-    public void disable();
-
-    /**
      * Returns a name for this command. This will be used by the user to select
      * this command.
      */
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandRegistryImpl.java	Wed Aug 22 16:28:44 2012 -0400
@@ -65,7 +65,6 @@
         if (cmd instanceof OSGiContext) {
             ((OSGiContext) cmd).setBundleContext(context);
         }
-        cmd.enable();
         proxy.registerService(cmd, cmd.getName());
         myRegisteredCommands.add(cmd);
     }
@@ -79,9 +78,6 @@
 
     @Override
     public void unregisterCommands() {
-        for (Command command : myRegisteredCommands) {
-            command.disable();
-        }
         proxy.unregisterAll();
     }
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/SimpleCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/SimpleCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -39,16 +39,6 @@
 public abstract class SimpleCommand implements Command {
 
     @Override
-    public void enable() {
-        /* NO-OP */
-    }
-
-    @Override
-    public void disable() {
-        /* NO-OP */
-    }
-
-    @Override
     public boolean isStorageRequired() {
         return true;
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/tools/BasicCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/tools/BasicCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -68,16 +68,6 @@
     }
 
     @Override
-    public void enable() {
-        /* NO-OP */
-    }
-
-    @Override
-    public void disable() {
-        /* NO-OP */
-    }
-
-    @Override
     public boolean isAvailableInShell() {
         return true;
     }
--- a/common/core/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/cli/CommandRegistryImplTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -131,9 +131,7 @@
 
         commandRegistry.unregisterCommands();
 
-        verify(cmd1).disable();
         verify(cmd1Reg).unregister();
-        verify(cmd2).disable();
         verify(cmd2Reg).unregister();
 
         verifyNoMoreInteractions(bundleContext);
--- a/common/core/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/tools/BasicCommandTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -64,11 +64,6 @@
             }
 
             @Override
-            public void disable() {
-                // Move along
-            }
-
-            @Override
             public String getName() {
                 return null;
             }
--- a/common/test/src/main/java/com/redhat/thermostat/test/cli/TestCommand.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/common/test/src/main/java/com/redhat/thermostat/test/cli/TestCommand.java	Wed Aug 22 16:28:44 2012 -0400
@@ -61,7 +61,6 @@
 
     public static interface Handle {
         public void run(CommandContext ctx) throws CommandException;
-        public void stop();
     }
 
     public TestCommand(String name) {
@@ -81,18 +80,6 @@
     }
 
     @Override
-    public void enable() {
-        // TODO what do we do here?
-    }
-
-    @Override
-    public void disable() {
-        if (handle != null) {
-            handle.stop();
-        }
-    }
-
-    @Override
     public String getName() {
         return name;
     }
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/Launcher.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/Launcher.java	Wed Aug 22 16:28:44 2012 -0400
@@ -36,6 +36,11 @@
 
 package com.redhat.thermostat.launcher;
 
+import java.util.Collection;
+
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
 /**
  * Launcher is the main entry point for all Thermostat commands.
  */
@@ -43,11 +48,19 @@
 
     /**
      * Invoked in order to start a command, either when Thermostat starts, or within
-     * the thermostat shell.
+     * the thermostat shell.  Equivalent to calling run(null).
      */
     void run();
 
     /**
+     * Invoked in order to start a command, either when Thermostat starts, or within
+     * the thermostat shell.  If the command being run happens to be a BasicCommand,
+     * and the argument is non-null, the listeners will be added to the command for
+     * life cycle notifications.  Otherwise, the argument is ignored.
+     */
+    void run(Collection<ActionListener<ApplicationState>> listeners);
+
+    /**
      * Should be set before calling run()
      * @param command line arguments to the program
      */
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Wed Aug 22 16:28:44 2012 -0400
@@ -47,6 +47,8 @@
 import org.osgi.framework.BundleException;
 
 import com.redhat.thermostat.bundles.OSGiRegistryService;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
@@ -60,6 +62,8 @@
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.config.InvalidConfigurationException;
 import com.redhat.thermostat.common.storage.ConnectionException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.tools.BasicCommand;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.CommonCommandOptions;
@@ -91,6 +95,11 @@
 
     @Override
     public synchronized void run() {
+        run(null);
+    }
+
+    @Override
+    public synchronized void run(Collection<ActionListener<ApplicationState>> listeners) {
         usageCount++;
         try {
             argsBarrier.acquire();
@@ -108,7 +117,7 @@
                 Version coreVersion = new Version();
                 cmdCtxFactory.getConsole().getOutput().println(coreVersion.getVersionInfo());
             } else {
-                runCommandFromArguments();
+                runCommandFromArguments(listeners);
             }
         } finally {
             args = null;
@@ -163,22 +172,22 @@
     }
 
     private void runHelpCommand() {
-        runCommand("help", new String[0]);
+        runCommand("help", new String[0], null);
     }
 
-    private void runCommandFromArguments() {
-        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length));
+    private void runCommandFromArguments(Collection<ActionListener<ApplicationState>> listeners) {
+        runCommand(args[0], Arrays.copyOfRange(args, 1, args.length), listeners);
     }
 
-    private void runCommand(String cmdName, String[] cmdArgs) {
+    private void runCommand(String cmdName, String[] cmdArgs, Collection<ActionListener<ApplicationState>> listeners) {
         try {
-            parseArgsAndRunCommand(cmdName, cmdArgs);
+            parseArgsAndRunCommand(cmdName, cmdArgs, listeners);
         } catch (CommandException e) {
             cmdCtxFactory.getConsole().getError().println(e.getMessage());
         }
     }
 
-    private void parseArgsAndRunCommand(String cmdName, String[] cmdArgs) throws CommandException {
+    private void parseArgsAndRunCommand(String cmdName, String[] cmdArgs, Collection<ActionListener<ApplicationState>> listeners) throws CommandException {
 
         PrintStream out = cmdCtxFactory.getConsole().getOutput();
         try {
@@ -198,6 +207,13 @@
             runHelpCommand();
             return;
         }
+        if (listeners != null && cmd instanceof BasicCommand) {
+            BasicCommand basicCmd = (BasicCommand) cmd;
+            ActionNotifier<ApplicationState> notifier = basicCmd.getNotifier();
+            for (ActionListener<ApplicationState> listener : listeners) {
+                notifier.addActionListener(listener);
+            }
+        }
         CommonCommandOptions commonOpts = new CommonCommandOptions();
         Collection<ArgumentSpec> acceptedOptions = commonOpts.getAcceptedOptionsFor(cmd);
         Arguments args = parseCommandArguments(cmdArgs, acceptedOptions);
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java	Wed Aug 22 19:23:39 2012 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java	Wed Aug 22 16:28:44 2012 -0400
@@ -43,7 +43,9 @@
 import static org.mockito.Mockito.when;
 
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -61,6 +63,8 @@
 import org.powermock.modules.junit4.PowerMockRunner;
 
 import com.redhat.thermostat.bundles.OSGiRegistryService;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.ApplicationInfo;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
@@ -73,6 +77,8 @@
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.common.locale.LocaleResources;
 import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.tools.BasicCommand;
 import com.redhat.thermostat.launcher.internal.HelpCommand;
 import com.redhat.thermostat.launcher.internal.LauncherImpl;
 import com.redhat.thermostat.test.TestCommandContextFactory;
@@ -106,10 +112,6 @@
             Arguments args = ctx.getArguments();
             ctx.getConsole().getOutput().print(args.getArgument("arg1") + ", " + args.getArgument("arg2"));
         }
-
-        @Override
-        public void stop() { /* N0-OP */ }
-
     }
 
     private static class TestCmd2 implements TestCommand.Handle {
@@ -118,11 +120,6 @@
             Arguments args = ctx.getArguments();
             ctx.getConsole().getOutput().print(args.getArgument("arg4") + ": " + args.getArgument("arg3"));
         }
-
-        @Override
-        public void stop() {
-            /* NO-OP */
-        }
     }
 
     private TestCommandContextFactory  ctxFactory;
@@ -130,6 +127,7 @@
     private BundleContext bundleContext;
     private TestTimerFactory timerFactory;
     private OSGiRegistryService registry;
+    private ActionNotifier<ApplicationState> notifier;
 
     @Before
     public void setUp() {
@@ -158,7 +156,14 @@
         cmd3.setStorageRequired(true);
         cmd3.setDescription("description 3");
 
-        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3));
+        BasicCommand basicCmd = mock(BasicCommand.class);
+        when(basicCmd.getName()).thenReturn("basic");
+        when(basicCmd.getDescription()).thenReturn("nothing that means anything");
+        when(basicCmd.isStorageRequired()).thenReturn(false);
+        notifier = mock(ActionNotifier.class);
+        when(basicCmd.getNotifier()).thenReturn(notifier);
+
+        ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3, basicCmd));
 
         registry = mock(OSGiRegistryService.class);
     }
@@ -197,6 +202,7 @@
     public void testMainNoArgs() {
         String expected = "list of commands:\n\n"
                         + " help          show help for a given command or help overview\n"
+                        + " basic         nothing that means anything\n"
                         + " test1         description 1\n"
                         + " test2         description 2\n"
                         + " test3         description 3\n";
@@ -215,6 +221,7 @@
         String expected = "unknown command '--help'\n"
             + "list of commands:\n\n"
             + " help          show help for a given command or help overview\n"
+            + " basic         nothing that means anything\n"
             + " test1         description 1\n"
             + " test2         description 2\n"
             + " test3         description 3\n";
@@ -226,6 +233,7 @@
         String expected = "unknown command '-help'\n"
             + "list of commands:\n\n"
             + " help          show help for a given command or help overview\n"
+            + " basic         nothing that means anything\n"
             + " test1         description 1\n"
             + " test2         description 2\n"
             + " test3         description 3\n";
@@ -237,6 +245,7 @@
         String expected = "unknown command 'foobarbaz'\n"
             + "list of commands:\n\n"
             + " help          show help for a given command or help overview\n"
+            + " basic         nothing that means anything\n"
             + " test1         description 1\n"
             + " test2         description 2\n"
             + " test3         description 3\n";
@@ -248,6 +257,7 @@
         String expected = "unknown command 'foo'\n"
             + "list of commands:\n\n"
             + " help          show help for a given command or help overview\n"
+            + " basic         nothing that means anything\n"
             + " test1         description 1\n"
             + " test2         description 2\n"
             + " test3         description 3\n";
@@ -263,8 +273,6 @@
                 throw new CommandException("test error");
             }
 
-            @Override
-            public void stop() { /* NO-OP */ }
         });
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(errorCmd));
 
@@ -355,4 +363,19 @@
         assertEquals(expectedVersionInfo, ctxFactory.getOutput());
         assertTrue(timerFactory.isShutdown());
     }
+
+    @Test
+    public void verifyListenersAdded() {
+        ActionListener<ApplicationState> listener = mock(ActionListener.class);
+        Collection<ActionListener<ApplicationState>> listeners = new ArrayList<>();
+        listeners.add(listener);
+        String[] args = new String[] {"basic"};
+        LauncherImpl launcher = new LauncherImpl(bundleContext, ctxFactory, registry);
+        Keyring keyring = mock(Keyring.class);
+        launcher.setPreferences(new ClientPreferences(keyring));
+
+        launcher.setArgs(args);
+        launcher.run(listeners);
+        verify(notifier).addActionListener(listener);
+    }
 }