changeset 636:9e4437930235

Add DB connection service This should improve usability of the Thermostat shell. In particular in a remote setup. The current situation is that if a shell is fired up it does not make a connection to storage. So if a user has storage set up to bind to a "public" IP, 192.1.1.129 say, then mongo requires username/password for connections. This results in bad usability of the shell: ./bin/thermostat shell Thermostat > ping Could not connect to db mongodb://192.168.1.14:27518 (creds are missing or incorrect host IP in user prefs) Thermostat > ping --username blah --password blah -d mongodb://example.org (works) Thermostat > list-vms --username blah --password blah -d ... (i.e. a user needs to pass credential info for every command on the shell). This patch introduces a "connect" and "disconnect" command which establishes a DB connection on demand and registers a DbService. The above shell session becomes: ./bin/thermostat shell Thermostat > ping Could not connect to db mongodb://192.168.1.14:27518 Thermostat > connect -d mongodb://192.168.1.14:27518 -u blah -p blah Thermostat > ping agent-id (works) Thermostat > list-vms (works) Thermostat > disconnect (disconnects from mongo storage at 192.168.1.14:27518) Thermostat > ping Could not connect to db mongodb://192.168.1.14:27518 Details of the patch: ----------------------------------------------------------------- 1. It removes AppContextSetup/AppContextSetupImpl 2. Modifies LauncherImpl in order to use the new DbService (i.e. other use cases remain the same) 3. OSGIUtils now returns the ServiceRegistration for registered services. 4. Added a method which allows services to be null to OSGIUtils. Existing getService() throws a NPE if the service reference is null (bundleContext.getServiceReference(clazz)). 5. Adds ConnectCommand ("connect" on shell) 6. Adds DisconnectCommand ("disconnect" on shell) 7. Tests Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-September/003341.html PR1164
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 21 Sep 2012 18:17:41 +0200
parents bb3d671fe253
children 2e7690c2c441
files common/core/src/main/java/com/redhat/thermostat/common/cli/AppContextSetupImpl.java common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContext.java common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextFactory.java common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextImpl.java common/core/src/main/java/com/redhat/thermostat/common/cli/ConnectionConfiguration.java common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java common/core/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java common/core/src/test/java/com/redhat/thermostat/common/cli/CommandContextFactoryTest.java launcher/src/main/java/com/redhat/thermostat/launcher/CommonCommandOptions.java launcher/src/main/java/com/redhat/thermostat/launcher/DbService.java launcher/src/main/java/com/redhat/thermostat/launcher/DbServiceFactory.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/ConnectionConfiguration.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/DbServiceImpl.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/DbServiceTest.java tools/src/main/java/com/redhat/thermostat/tools/LocaleResources.java tools/src/main/java/com/redhat/thermostat/tools/cli/ConnectCommand.java tools/src/main/java/com/redhat/thermostat/tools/cli/DisconnectCommand.java tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command tools/src/main/resources/com/redhat/thermostat/tools/strings.properties tools/src/test/java/com/redhat/thermostat/tools/cli/ConnectCommandTest.java tools/src/test/java/com/redhat/thermostat/tools/cli/DisconnectCommandTest.java tools/src/test/java/com/redhat/thermostat/tools/cli/ListVMsCommandTest.java tools/src/test/java/com/redhat/thermostat/tools/cli/VMInfoCommandTest.java tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java
diffstat 26 files changed, 1101 insertions(+), 226 deletions(-) [+]
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/AppContextSetupImpl.java	Thu Sep 20 18:07:27 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.common.cli;
-
-import com.redhat.thermostat.common.appctx.ApplicationContext;
-import com.redhat.thermostat.common.config.StartupConfiguration;
-import com.redhat.thermostat.common.dao.DAOFactory;
-import com.redhat.thermostat.common.dao.MongoDAOFactory;
-import com.redhat.thermostat.common.storage.Connection;
-import com.redhat.thermostat.common.storage.MongoStorageProvider;
-import com.redhat.thermostat.common.storage.StorageProvider;
-
-class AppContextSetupImpl implements AppContextSetup {
-
-    @Override
-    public void setupAppContext(String dbUrl, String username, String password) {
-        StartupConfiguration config = new ConnectionConfiguration(dbUrl, username, password);
-        
-        StorageProvider connProv = new MongoStorageProvider(config);
-        DAOFactory daoFactory = new MongoDAOFactory(connProv);
-        Connection connection = daoFactory.getConnection();
-        connection.connect();
-        ApplicationContext.getInstance().setDAOFactory(daoFactory);
-
-    }
-
-}
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContext.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContext.java	Fri Sep 21 18:17:41 2012 +0200
@@ -45,8 +45,6 @@
 
     CommandRegistry getCommandRegistry();
 
-    AppContextSetup getAppContextSetup();
-
     CommandContextFactory getCommandContextFactory();
 
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextFactory.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextFactory.java	Fri Sep 21 18:17:41 2012 +0200
@@ -51,10 +51,6 @@
         return new CommandContextImpl(args, this);
     }
 
-    protected AppContextSetup getAppContextSetup() {
-        return new AppContextSetupImpl();
-    }
-
     public CommandRegistry getCommandRegistry() {
         return commandRegistry;
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextImpl.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/CommandContextImpl.java	Fri Sep 21 18:17:41 2012 +0200
@@ -62,11 +62,6 @@
     }
 
     @Override
-    public AppContextSetup getAppContextSetup() {
-        return commandContextFactory.getAppContextSetup();
-    }
-
-    @Override
     public CommandContextFactory getCommandContextFactory() {
         return commandContextFactory;
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/cli/ConnectionConfiguration.java	Thu Sep 20 18:07:27 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.common.cli;
-
-import com.redhat.thermostat.common.config.StartupConfiguration;
-
-class ConnectionConfiguration implements StartupConfiguration, AuthenticationConfiguration {
-
-    private String dbUrl;
-    private String username;
-    private String password;
-
-    ConnectionConfiguration(String dbUrl, String username, String password) {
-        this.dbUrl = dbUrl;
-        this.username = username;
-        this.password = password;
-    }
-
-    @Override
-    public String getDBConnectionString() {
-        return dbUrl;
-    }
-
-    @Override
-    public String getUsername() {
-        return username;
-    }
-
-    @Override
-    public String getPassword() {
-        return password;
-    }
-}
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java	Fri Sep 21 18:17:41 2012 +0200
@@ -41,6 +41,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 
 public class OSGIUtils {
     
@@ -55,6 +56,13 @@
     	instance = utils;
     }
 
+    /**
+     * Gets a service and never return null. It will throw a NullPointerException
+     * before it returns.
+     * 
+     * @param clazz
+     * @return The best matching service implementation for the requested clazz.
+     */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     public <E extends Object> E getService(Class<E> clazz) {
         BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
@@ -62,12 +70,24 @@
         return (E) ctx.getService(ref);
     }
     
-    public <E extends Object> void registerService(Class<? extends E> serviceClass, E service) {
-        registerService(serviceClass, service, null);
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public <E extends Object> E getServiceAllowNull(Class<E> clazz) {
+        BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
+        ServiceReference ref = ctx.getServiceReference(clazz.getName());
+        if (ref == null) {
+            return null;
+        }
+        return (E) ctx.getService(ref);
+    }
+    
+    @SuppressWarnings("rawtypes")
+    public <E extends Object> ServiceRegistration registerService(Class<? extends E> serviceClass, E service) {
+        return registerService(serviceClass, service, null);
     }
         
-    public <E extends Object> void registerService(Class<? extends E> serviceClass, E service, Dictionary<String, ?> properties) {
+    @SuppressWarnings("rawtypes")
+    public <E extends Object> ServiceRegistration registerService(Class<? extends E> serviceClass, E service, Dictionary<String, ?> properties) {
         BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
-        ctx.registerService(serviceClass.getName(), service, properties);
+        return ctx.registerService(serviceClass.getName(), service, properties);
     }
 }
--- a/common/core/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/test/TestCommandContextFactory.java	Fri Sep 21 18:17:41 2012 +0200
@@ -68,14 +68,6 @@
 
     private CommandRegistry commandRegistry = new TestCommandRegistry();
 
-    private AppContextSetup appContextSetup = new AppContextSetup() {
-
-        @Override
-        public void setupAppContext(String dbUrl, String username, String password) {
-            // We do nothing for now.
-        }
-    };
-
     private TestConsole console;
     private PipedOutputStream inOut;
 
@@ -118,11 +110,6 @@
             }
 
             @Override
-            public AppContextSetup getAppContextSetup() {
-                return TestCommandContextFactory.this.getAppContextSetup();
-            }
-
-            @Override
             public CommandContextFactory getCommandContextFactory() {
                 return TestCommandContextFactory.this;
             }
@@ -135,11 +122,6 @@
         return commandRegistry;
     }
 
-    @Override
-    protected AppContextSetup getAppContextSetup() {
-        return appContextSetup;
-    }
-
     public String getOutput() {
         return new String(out.toByteArray());
     }
--- a/common/core/src/test/java/com/redhat/thermostat/common/cli/CommandContextFactoryTest.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/cli/CommandContextFactoryTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.common.cli;
 
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
@@ -67,7 +66,6 @@
         Arguments args = mock(Arguments.class);
         CommandContext ctx = cmdCtxFactory.createContext(args);
         assertSame(args, ctx.getArguments());
-        assertNotNull(ctx.getAppContextSetup());
         assertSame(cmdCtxFactory, ctx.getCommandContextFactory());
         assertSame(cmdCtxFactory.getCommandRegistry(), ctx.getCommandRegistry());
         assertSame(cmdCtxFactory.getConsole(), ctx.getConsole());
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/CommonCommandOptions.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/CommonCommandOptions.java	Fri Sep 21 18:17:41 2012 +0200
@@ -14,9 +14,9 @@
     public static final String USERNAME_ARG = "username";
     public static final String PASSWORD_ARG = "password";
 
-    private static final String DB_URL_DESC = "the URL of the storage to connect to";
-    private static final String USERNAME_DESC = "the username to use for authentication";
-    private static final String PASSWORD_DESC = "the password to use for authentication";
+    public static final String DB_URL_DESC = "the URL of the storage to connect to";
+    public static final String USERNAME_DESC = "the username to use for authentication";
+    public static final String PASSWORD_DESC = "the password to use for authentication";
 
     public static final String LOG_LEVEL_ARG = "logLevel";
     private static final String LOG_LEVEL_DESC = "log level";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/DbService.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher;
+
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.common.storage.ConnectionException;
+
+public interface DbService {
+
+    /**
+     * Connects to the given database.
+     * 
+     * @throws ConnectionException If DB connection cannot be established.
+     */
+    void connect() throws ConnectionException;
+    
+    
+    /**
+     * Disconnects from the database.
+     * 
+     * @throws ConnectionException
+     */
+    void disconnect() throws ConnectionException;
+    
+    /**
+     * Get the registration for this service.
+     * @return
+     */
+    @SuppressWarnings("rawtypes")
+    ServiceRegistration getServiceRegistration();
+    
+    /**
+     * Set the registration suitable for this service.
+     */
+    @SuppressWarnings("rawtypes")
+    void setServiceRegistration(ServiceRegistration registration);
+} 
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/DbServiceFactory.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher;
+
+import com.redhat.thermostat.launcher.internal.DbServiceImpl;
+
+/**
+ * Factory in order to be able to hide the BbService implementation.
+ *
+ */
+public class DbServiceFactory {
+
+    public static DbService createDbService(String username, String password, String dbUrl) {
+        return DbServiceImpl.create(username, password, dbUrl);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/ConnectionConfiguration.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import com.redhat.thermostat.common.cli.AuthenticationConfiguration;
+import com.redhat.thermostat.common.config.StartupConfiguration;
+
+class ConnectionConfiguration implements StartupConfiguration, AuthenticationConfiguration {
+
+    private String dbUrl;
+    private String username;
+    private String password;
+
+    ConnectionConfiguration(String dbUrl, String username, String password) {
+        this.dbUrl = dbUrl;
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public String getDBConnectionString() {
+        return dbUrl;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/DbServiceImpl.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import java.util.Objects;
+
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.config.StartupConfiguration;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.MongoDAOFactory;
+import com.redhat.thermostat.common.storage.Connection;
+import com.redhat.thermostat.common.storage.ConnectionException;
+import com.redhat.thermostat.common.storage.MongoStorageProvider;
+import com.redhat.thermostat.common.storage.StorageProvider;
+import com.redhat.thermostat.launcher.DbService;
+
+public class DbServiceImpl implements DbService {
+    
+    private String username;
+    private String password;
+    private String dbUrl;
+    @SuppressWarnings("rawtypes")
+    private ServiceRegistration registration;
+    
+    
+    DbServiceImpl(String username, String password, String dbUrl) {
+        this.username = username;
+        this.password = password;
+        this.dbUrl = dbUrl;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.redhat.thermostat.launcher.DbService#connect()
+     */
+    public void connect() throws ConnectionException {
+        StartupConfiguration config = new ConnectionConfiguration(dbUrl, username, password);
+        
+        StorageProvider connProv = new MongoStorageProvider(config);
+        DAOFactory daoFactory = new MongoDAOFactory(connProv);
+        Connection connection = daoFactory.getConnection();
+        connection.connect();
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+    }
+    
+    /*
+     * (non-Javadoc)
+     * @see com.redhat.thermostat.launcher.DbService#disconnect()
+     */
+    public void disconnect() throws ConnectionException {
+        DAOFactory factory = ApplicationContext.getInstance().getDAOFactory();
+        Connection connection = factory.getConnection();
+        connection.disconnect();
+        ApplicationContext.getInstance().setDAOFactory(null);
+    }
+    
+    
+    
+    /**
+     * Factory method for creating a DbService instance.
+     * 
+     * @param username
+     * @param password
+     * @param dbUrl
+     * @return
+     */
+    public static DbService create(String username, String password, String dbUrl) {
+        return new DbServiceImpl(username, password, dbUrl);
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public ServiceRegistration getServiceRegistration() {
+        return registration;
+    }
+
+    @Override
+    public void setServiceRegistration(@SuppressWarnings("rawtypes") ServiceRegistration registration) {
+        this.registration = Objects.requireNonNull(registration);
+    }
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Fri Sep 21 18:17:41 2012 +0200
@@ -45,6 +45,7 @@
 
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceRegistration;
 
 import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.ActionListener;
@@ -67,6 +68,8 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.CommonCommandOptions;
+import com.redhat.thermostat.launcher.DbService;
+import com.redhat.thermostat.launcher.DbServiceFactory;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
@@ -253,29 +256,37 @@
         return args;
     }
 
+    @SuppressWarnings("rawtypes")
     private CommandContext setupCommandContext(Command cmd, Arguments args) throws CommandException {
 
+        CommandContext ctx = cmdCtxFactory.createContext(args);
+        
         if (prefs == null) {
             prefs = new ClientPreferences(OSGIUtils.getInstance().getService(Keyring.class));
         }
         
-        CommandContext ctx = cmdCtxFactory.createContext(args);
         if (cmd.isStorageRequired()) {
-            String dbUrl = ctx.getArguments().getArgument(CommonCommandOptions.DB_URL_ARG);
-            if (dbUrl == null) {
-                dbUrl = prefs.getConnectionUrl();
-            }
-            String username = ctx.getArguments().getArgument(CommonCommandOptions.USERNAME_ARG);
-            String password = ctx.getArguments().getArgument(CommonCommandOptions.PASSWORD_ARG);
-            try {
-                ctx.getAppContextSetup().setupAppContext(dbUrl, username, password);
-            } catch (ConnectionException ex) {
-                throw new CommandException("Could not connect to: " + dbUrl, ex);
+            DbService service = OSGIUtils.getInstance().getServiceAllowNull(DbService.class);
+            if (service == null) {
+                String dbUrl = ctx.getArguments().getArgument(CommonCommandOptions.DB_URL_ARG);
+                if (dbUrl == null) {
+                    dbUrl = prefs.getConnectionUrl();
+                }
+                String username = ctx.getArguments().getArgument(CommonCommandOptions.USERNAME_ARG);
+                String password = ctx.getArguments().getArgument(CommonCommandOptions.PASSWORD_ARG);
+                service = DbServiceFactory.createDbService(username, password, dbUrl);
+                try {
+                    service.connect();
+                } catch (ConnectionException ex) {
+                    throw new CommandException("Could not connect to: " + dbUrl, ex);
+                }
+                ServiceRegistration registration = OSGIUtils.getInstance().registerService(DbService.class, service);
+                service.setServiceRegistration(registration);
             }
         }
         return ctx;
     }
-    
+
     private boolean isVersionQuery() {
         return args[0].equals(Version.VERSION_OPTION);
     }
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/LauncherTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -41,6 +41,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.any;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -58,6 +59,7 @@
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
 import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
@@ -69,7 +71,6 @@
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
-import com.redhat.thermostat.common.cli.AppContextSetup;
 import com.redhat.thermostat.common.cli.Arguments;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
@@ -79,6 +80,7 @@
 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.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.internal.HelpCommand;
 import com.redhat.thermostat.launcher.internal.LauncherImpl;
 import com.redhat.thermostat.test.TestCommandContextFactory;
@@ -88,7 +90,7 @@
 import com.redhat.thermostat.utils.keyring.KeyringProvider;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(FrameworkUtil.class)
+@PrepareForTest({FrameworkUtil.class, DbServiceFactory.class})
 public class LauncherTest {
     
     private static String defaultKeyringProvider;
@@ -123,7 +125,6 @@
     }
 
     private TestCommandContextFactory  ctxFactory;
-    private AppContextSetup appContextSetup;
     private BundleContext bundleContext;
     private TestTimerFactory timerFactory;
     private OSGiRegistry registry;
@@ -169,22 +170,15 @@
     }
 
     private void setupCommandContextFactory() {
-        appContextSetup = mock(AppContextSetup.class);
         Bundle sysBundle = mock(Bundle.class);
         bundleContext = mock(BundleContext.class);
         when(bundleContext.getBundle(0)).thenReturn(sysBundle);
-        ctxFactory = new TestCommandContextFactory(bundleContext) {
-            @Override
-            protected AppContextSetup getAppContextSetup() {
-                return appContextSetup;
-            }
-        };
+        ctxFactory = new TestCommandContextFactory(bundleContext);
     }
 
 
     @After
     public void tearDown() {
-        appContextSetup = null;
         ctxFactory = null;
         ApplicationContextUtil.resetApplicationContext();
     }
@@ -300,32 +294,63 @@
     public void verifyStorageCommandSetsUpDAOFactory() {
         LauncherImpl launcher = new LauncherImpl(bundleContext, ctxFactory, registry);
         Keyring keyring = mock(Keyring.class);
+        Bundle sysBundle = mock(Bundle.class);
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(OSGIUtils.class)).thenReturn(sysBundle);
+        when(sysBundle.getBundleContext()).thenReturn(bundleContext);
+        PowerMockito.mockStatic(DbServiceFactory.class);
+        String dbUrl = "mongo://fluff:12345";
+        DbService dbService = mock(DbService.class);
+        when(DbServiceFactory.createDbService(null, null, dbUrl)).thenReturn(dbService);
         launcher.setPreferences(new ClientPreferences(keyring));
         
-        launcher.setArgs(new String[] { "test3" , "--dbUrl", "mongo://fluff:12345" });
+        launcher.setArgs(new String[] { "test3" , "--dbUrl", dbUrl });
         launcher.run();
-        verify(appContextSetup).setupAppContext("mongo://fluff:12345", null, null);
+        verify(dbService).connect();
+        verify(dbService).setServiceRegistration(any(ServiceRegistration.class));
     }
 
     @Test
     public void verifyStorageCommandSetsUpDAOFactoryWithAuth() {
         LauncherImpl launcher = new LauncherImpl(bundleContext, ctxFactory, registry);
         Keyring keyring = mock(Keyring.class);
+        Bundle sysBundle = mock(Bundle.class);
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(OSGIUtils.class)).thenReturn(sysBundle);
+        when(sysBundle.getBundleContext()).thenReturn(bundleContext);
+        PowerMockito.mockStatic(DbServiceFactory.class);
+        String dbUrl = "mongo://fluff:12345";
+        String testUser = "testUser";
+        String testPasswd = "testPassword";
+        DbService dbService = mock(DbService.class);
+        when(DbServiceFactory.createDbService(testUser, testPasswd, dbUrl)).thenReturn(dbService);
+        
         launcher.setPreferences(new ClientPreferences(keyring));
         
-        launcher.setArgs(new String[] { "test3" , "--dbUrl", "mongo://fluff:12345", "--username", "testuser", "--password", "testpwd" });
+        launcher.setArgs(new String[] { "test3" , "--dbUrl", dbUrl, "--username", testUser, "--password", testPasswd });
         launcher.run();
-        verify(appContextSetup).setupAppContext("mongo://fluff:12345", "testuser", "testpwd");
+        verify(dbService).connect();
+        verify(dbService).setServiceRegistration(any(ServiceRegistration.class));
     }
 
     public void verifyPrefsAreUsed() {
         ClientPreferences prefs = mock(ClientPreferences.class);
-        when(prefs.getConnectionUrl()).thenReturn("mongo://fluff:12345");
+        String dbUrl = "mongo://fluff:12345";
+        when(prefs.getConnectionUrl()).thenReturn(dbUrl);
         LauncherImpl l = new LauncherImpl(bundleContext, ctxFactory, registry);
+        Bundle sysBundle = mock(Bundle.class);
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(OSGIUtils.class)).thenReturn(sysBundle);
+        when(sysBundle.getBundleContext()).thenReturn(bundleContext);
+        PowerMockito.mockStatic(DbServiceFactory.class);
+        DbService dbService = mock(DbService.class);
+        // this makes sure that dbUrl is indeed retrieved from preferences
+        when(DbServiceFactory.createDbService(null, null, dbUrl)).thenReturn(dbService);
         l.setPreferences(prefs);
         l.setArgs(new String[] { "test3" });
         l.run();
-        verify(appContextSetup).setupAppContext("mongo://fluff:12345", null, null);
+        verify(dbService).connect();
+        verify(dbService).setServiceRegistration(any(ServiceRegistration.class));
     }
     
     @Test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/DbServiceTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import static org.junit.Assert.fail;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.launcher.DbService;
+
+public class DbServiceTest {
+    
+    private DbService dbService;
+    
+    @Before
+    public void setup() {
+        String dbUrl = "mongodb://testhost.example.com:19223";
+        dbService = new DbServiceImpl("tester", "testerpassword", dbUrl);
+    }
+    
+    @After
+    public void teardown() {
+        dbService = null;
+    }
+    
+    @Test
+    public void testSetRegistrationRegistryThrowsException() {
+        try {
+            dbService.setServiceRegistration(null);
+            fail("Null registrations not allowed");
+        } catch (Exception e) {
+            // pass
+        }
+    }
+
+}
--- a/tools/src/main/java/com/redhat/thermostat/tools/LocaleResources.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/LocaleResources.java	Fri Sep 21 18:17:41 2012 +0200
@@ -50,6 +50,14 @@
 
     COMMAND_VM_STAT_DESCRIPTION,
     COMMAND_VM_STAT_ARGUMENT_CONTINUOUS_DESCRIPTION,
+    
+    COMMAND_CONNECT_DESCRIPTION,
+    COMMAND_CONNECT_ALREADY_CONNECTED,
+    COMMAND_CONNECT_FAILED_TO_CONNECT,
+    
+    COMMAND_DISCONNECT_DESCRIPTION,
+    COMMAND_DISCONNECT_NOT_CONNECTED,
+    COMMAND_DISCONNECT_ERROR,
 
     VM_INFO_PROCESS_ID,
     VM_INFO_START_TIME,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/ConnectCommand.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.tools.cli;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleArgumentSpec;
+import com.redhat.thermostat.common.cli.SimpleCommand;
+import com.redhat.thermostat.common.config.ClientPreferences;
+import com.redhat.thermostat.common.storage.ConnectionException;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.CommonCommandOptions;
+import com.redhat.thermostat.launcher.DbService;
+import com.redhat.thermostat.launcher.DbServiceFactory;
+import com.redhat.thermostat.tools.LocaleResources;
+import com.redhat.thermostat.tools.Translate;
+import com.redhat.thermostat.utils.keyring.Keyring;
+
+/**
+ * Command in order to persistently connect to a database. Available only in
+ * shell.
+ * 
+ * This commands registers a connection service. If this service is available,
+ * it can be used to retrieve a DB connection.
+ * 
+ */
+public class ConnectCommand extends SimpleCommand {
+
+    private static final String NAME = "connect";
+    private static final String USAGE = "connect -d <url> [-u <username>] [-p <password>]";
+    private static final String DESC = Translate.localize(LocaleResources.COMMAND_CONNECT_DESCRIPTION);
+    
+    private ClientPreferences prefs;
+    
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        DbService service = OSGIUtils.getInstance().getServiceAllowNull(DbService.class);
+        if (service != null) {
+            // Already connected, bail out
+            throw new CommandException(Translate.localize(LocaleResources.COMMAND_CONNECT_ALREADY_CONNECTED));
+        }
+        if (prefs == null) {
+            prefs = new ClientPreferences(OSGIUtils.getInstance().getService(Keyring.class));
+        }
+        String dbUrl = ctx.getArguments().getArgument(CommonCommandOptions.DB_URL_ARG);
+        if (dbUrl == null) {
+            dbUrl = prefs.getConnectionUrl();
+        }
+        String username = ctx.getArguments().getArgument(CommonCommandOptions.USERNAME_ARG);
+        String password = ctx.getArguments().getArgument(CommonCommandOptions.PASSWORD_ARG);
+        service = DbServiceFactory.createDbService(username, password, dbUrl);
+        try {
+            service.connect();
+        } catch (ConnectionException ex) {
+            throw new CommandException(Translate.localize(LocaleResources.COMMAND_CONNECT_FAILED_TO_CONNECT, dbUrl), ex);
+        }
+        ServiceRegistration registration = OSGIUtils.getInstance().registerService(DbService.class, service);
+        service.setServiceRegistration(registration);
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESC;
+    }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
+
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        List<ArgumentSpec> acceptedArgs = new ArrayList<>();
+        acceptedArgs.add(new SimpleArgumentSpec(CommonCommandOptions.DB_URL_ARG, "d", CommonCommandOptions.DB_URL_DESC, true, true));
+        acceptedArgs.add(new SimpleArgumentSpec(CommonCommandOptions.USERNAME_ARG, "u", CommonCommandOptions.USERNAME_DESC, false, true));
+        acceptedArgs.add(new SimpleArgumentSpec(CommonCommandOptions.PASSWORD_ARG, "p", CommonCommandOptions.PASSWORD_DESC, false, true));
+        return acceptedArgs;
+    }
+    
+    @Override
+    public boolean isAvailableOutsideShell() {
+        return false;
+    }
+    
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/DisconnectCommand.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.tools.cli;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleCommand;
+import com.redhat.thermostat.common.storage.ConnectionException;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.DbService;
+import com.redhat.thermostat.tools.LocaleResources;
+import com.redhat.thermostat.tools.Translate;
+
+public class DisconnectCommand extends SimpleCommand {
+    
+    private static final String NAME = "disconnect";
+    private static final String USAGE = NAME;
+    private static final String DESC = Translate.localize(LocaleResources.COMMAND_DISCONNECT_DESCRIPTION);
+    
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        DbService service = OSGIUtils.getInstance().getServiceAllowNull(DbService.class);
+        if (service == null) {
+            // not connected
+            throw new CommandException(Translate.localize(LocaleResources.COMMAND_DISCONNECT_NOT_CONNECTED));
+        }
+        try {
+            service.disconnect();
+        } catch (ConnectionException e) {
+            throw new CommandException(Translate.localize(LocaleResources.COMMAND_DISCONNECT_ERROR));
+        }
+        // remove DB service
+        ServiceRegistration registration = service.getServiceRegistration();
+        registration.unregister();
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESC;
+    }
+
+    @Override
+    public String getUsage() {
+        return USAGE;
+    }
+
+    @Override
+    public Collection<ArgumentSpec> getAcceptedArguments() {
+        return Collections.emptyList();
+    }
+    
+    @Override
+    public boolean isAvailableOutsideShell() {
+        return false;
+    }
+    
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+}
--- a/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/main/resources/META-INF/services/com.redhat.thermostat.common.cli.Command	Fri Sep 21 18:17:41 2012 +0200
@@ -2,3 +2,5 @@
 com.redhat.thermostat.tools.cli.ShellCommand
 com.redhat.thermostat.tools.cli.VMInfoCommand
 com.redhat.thermostat.tools.cli.VMStatCommand
+com.redhat.thermostat.tools.cli.ConnectCommand
+com.redhat.thermostat.tools.cli.DisconnectCommand
--- a/tools/src/main/resources/com/redhat/thermostat/tools/strings.properties	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/main/resources/com/redhat/thermostat/tools/strings.properties	Fri Sep 21 18:17:41 2012 +0200
@@ -11,6 +11,14 @@
 COMMAND_VM_STAT_DESCRIPTION = show various statistics about a VM
 COMMAND_VM_STAT_ARGUMENT_CONTINUOUS_DESCRIPTION = print data continuously
 
+COMMAND_CONNECT_DESCRIPTION = persistenly connect to a database
+COMMAND_CONNECT_ALREADY_CONNECTED = Already connected to storage. Please use disconnect command to disconnect.
+COMMAND_CONNECT_FAILED_TO_CONNECT = Could not connect to db {0}
+
+COMMAND_DISCONNECT_DESCRIPTION = disconnect from the currently used database
+COMMAND_DISCONNECT_NOT_CONNECTED = Not connected to storage. You may use the connect command for establishing connections.
+COMMAND_DISCONNECT_ERROR = Failed to disconnect from database.
+
 VM_INFO_PROCESS_ID = Process ID:
 VM_INFO_START_TIME = Start time:
 VM_INFO_STOP_TIME = Stop time:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/ConnectCommandTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.tools.cli;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.any;
+
+import java.util.Collection;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleArgumentSpec;
+import com.redhat.thermostat.common.cli.SimpleArguments;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.CommonCommandOptions;
+import com.redhat.thermostat.launcher.DbService;
+import com.redhat.thermostat.launcher.DbServiceFactory;
+import com.redhat.thermostat.test.TestCommandContextFactory;
+import com.redhat.thermostat.tools.LocaleResources;
+import com.redhat.thermostat.tools.Translate;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ OSGIUtils.class, DbServiceFactory.class })
+public class ConnectCommandTest {
+
+    private ConnectCommand cmd;
+    private TestCommandContextFactory cmdCtxFactory;
+    private BundleContext bundleContext;
+
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        setupCommandContextFactory();
+
+        cmd = new ConnectCommand();
+
+    }
+
+    private void setupCommandContextFactory() {
+        Bundle sysBundle = mock(Bundle.class);
+        bundleContext = mock(BundleContext.class);
+        when(bundleContext.getBundle(0)).thenReturn(sysBundle);
+        cmdCtxFactory = new TestCommandContextFactory(bundleContext);
+    }
+
+    @After
+    public void tearDown() {
+        cmdCtxFactory = null;
+        cmd = null;
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+    public void verifyConnectedThrowsException() {
+        DbService dbService = mock(DbService.class);
+        OSGIUtils utils = mock(OSGIUtils.class);
+        PowerMockito.mockStatic(OSGIUtils.class);
+        when(OSGIUtils.getInstance()).thenReturn(utils);
+        when(utils.getServiceAllowNull(DbService.class)).thenReturn(dbService);
+
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("--dbUrl", "fluff");
+        try {
+            cmd.run(cmdCtxFactory.createContext(args));
+        } catch (CommandException e) {
+            assertEquals(Translate.localize(LocaleResources.COMMAND_CONNECT_ALREADY_CONNECTED), e.getMessage());
+        }
+    }
+    
+    @Test
+    public void verifyNotConnectedConnects() throws CommandException {
+        OSGIUtils utils = mock(OSGIUtils.class);
+        PowerMockito.mockStatic(OSGIUtils.class);
+        when(OSGIUtils.getInstance()).thenReturn(utils);
+        when(utils.getServiceAllowNull(DbService.class)).thenReturn(null);
+
+        DbService dbService = mock(DbService.class);
+        PowerMockito.mockStatic(DbServiceFactory.class);
+        String username = "testuser";
+        String password = "testpassword";
+        String dbUrl = "mongodb://10.23.122.1:12578";
+        when(DbServiceFactory.createDbService(eq(username), eq(password), eq(dbUrl))).thenReturn(dbService);
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("dbUrl", dbUrl);
+        args.addArgument("username", username);
+        args.addArgument("password", password);
+        CommandContext ctx = cmdCtxFactory.createContext(args);
+        cmd.run(ctx);
+        verify(dbService).connect();
+        verify(dbService).setServiceRegistration(any(ServiceRegistration.class));
+    }
+    
+    @Test
+    public void testIsStorageRequired() {
+        assertFalse(cmd.isStorageRequired());
+    }
+
+    @Test
+    public void testIsNotAvailableOutsideShell() {
+        assertFalse(cmd.isAvailableOutsideShell());
+    }
+    
+    @Test
+    public void testIsAvailableInShell() {
+        assertTrue(cmd.isAvailableInShell());
+    }
+
+    @Test
+    public void testName() {
+        assertEquals("connect", cmd.getName());
+    }
+
+    @Test
+    public void testDescription() {
+        assertEquals("persistenly connect to a database", cmd.getDescription());
+    }
+
+    @Test
+    public void testUsage() {
+        String expected = "connect -d <url> [-u <username>] [-p <password>]";
+
+        assertEquals(expected, cmd.getUsage());
+    }
+
+    @Test
+    public void testAcceptedArguments() {
+        Collection<ArgumentSpec> args = cmd.getAcceptedArguments();
+        assertNotNull(args);
+        assertTrue(args.size() == 3);
+        assertTrue(args.contains(new SimpleArgumentSpec(CommonCommandOptions.DB_URL_ARG, "d", CommonCommandOptions.DB_URL_DESC, true, true)));
+        assertTrue(args.contains(new SimpleArgumentSpec(CommonCommandOptions.USERNAME_ARG, "u", CommonCommandOptions.USERNAME_DESC, false, true)));
+        assertTrue(args.contains(new SimpleArgumentSpec(CommonCommandOptions.PASSWORD_ARG, "p", CommonCommandOptions.PASSWORD_DESC, false, true)));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/DisconnectCommandTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.tools.cli;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collection;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.cli.ArgumentSpec;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleArguments;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.DbService;
+import com.redhat.thermostat.test.TestCommandContextFactory;
+import com.redhat.thermostat.tools.LocaleResources;
+import com.redhat.thermostat.tools.Translate;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ OSGIUtils.class, FrameworkUtil.class })
+public class DisconnectCommandTest {
+
+    private DisconnectCommand cmd;
+    private TestCommandContextFactory cmdCtxFactory;
+    private BundleContext bundleContext;
+
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        setupCommandContextFactory();
+
+        cmd = new DisconnectCommand();
+
+    }
+
+    private void setupCommandContextFactory() {
+        Bundle sysBundle = mock(Bundle.class);
+        bundleContext = mock(BundleContext.class);
+        when(bundleContext.getBundle(0)).thenReturn(sysBundle);
+        cmdCtxFactory = new TestCommandContextFactory(bundleContext);
+    }
+
+    @After
+    public void tearDown() {
+        cmdCtxFactory = null;
+        cmd = null;
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    @Test
+    public void verifyNotConnectedThrowsException() {
+        OSGIUtils utils = mock(OSGIUtils.class);
+        PowerMockito.mockStatic(OSGIUtils.class);
+        when(OSGIUtils.getInstance()).thenReturn(utils);
+        when(utils.getServiceAllowNull(DbService.class)).thenReturn(null);
+
+        try {
+            cmd.run(cmdCtxFactory.createContext(new SimpleArguments()));
+            fail("cmd.run() should have thrown exception.");
+        } catch (CommandException e) {
+            assertEquals(Translate.localize(LocaleResources.COMMAND_DISCONNECT_NOT_CONNECTED), e.getMessage());
+        }
+    }
+    
+    @SuppressWarnings({ "rawtypes" })
+    @Test
+    public void verifyConnectedDisconnectsAndUnregistersService() throws CommandException {
+        DbService dbService = mock(DbService.class);
+        OSGIUtils utils = mock(OSGIUtils.class);
+        PowerMockito.mockStatic(OSGIUtils.class);
+        when(OSGIUtils.getInstance()).thenReturn(utils);
+        when(utils.getServiceAllowNull(DbService.class)).thenReturn(dbService);
+        ServiceRegistration registration = mock(ServiceRegistration.class);
+        when(dbService.getServiceRegistration()).thenReturn(registration);
+        
+        CommandContext ctx = cmdCtxFactory.createContext(new SimpleArguments());
+        cmd.run(ctx);
+        verify(dbService).disconnect();
+        verify(registration).unregister();
+    }
+
+    @Test
+    public void testIsNotAvailableOutsideShell() {
+        assertFalse(cmd.isAvailableOutsideShell());
+    }
+    
+    @Test
+    public void testIsAvailableInShell() {
+        assertTrue(cmd.isAvailableInShell());
+    }
+    
+    @Test
+    public void testIsStorageRequired() {
+        assertFalse(cmd.isStorageRequired());
+    }
+
+    @Test
+    public void testName() {
+        assertEquals("disconnect", cmd.getName());
+    }
+
+    @Test
+    public void testDescription() {
+        assertEquals("disconnect from the currently used database", cmd.getDescription());
+    }
+
+    @Test
+    public void testUsage() {
+        String expected = "disconnect";
+        assertEquals(expected, cmd.getUsage());
+    }
+
+    @Test
+    public void testAcceptedArguments() {
+        Collection<ArgumentSpec> args = cmd.getAcceptedArguments();
+        assertNotNull(args);
+        assertTrue(args.size() == 0);
+    }
+}
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/ListVMsCommandTest.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/ListVMsCommandTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -39,8 +39,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -53,7 +53,6 @@
 
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
-import com.redhat.thermostat.common.cli.AppContextSetup;
 import com.redhat.thermostat.common.cli.ArgumentSpec;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
@@ -69,7 +68,6 @@
 public class ListVMsCommandTest {
 
     private ListVMsCommand cmd;
-    private AppContextSetup appContextSetup;
     private TestCommandContextFactory cmdCtxFactory;
     private HostInfoDAO hostsDAO;
     private VmInfoDAO vmsDAO;
@@ -86,13 +84,7 @@
     }
 
     private void setupCommandContextFactory() {
-        appContextSetup = mock(AppContextSetup.class);
-        cmdCtxFactory = new TestCommandContextFactory() {
-            @Override
-            protected AppContextSetup getAppContextSetup() {
-                return appContextSetup;
-            }
-        };
+        cmdCtxFactory = new TestCommandContextFactory();
     }
 
     private void setupDAOs() {
@@ -110,7 +102,6 @@
         hostsDAO = null;
         cmdCtxFactory = null;
         cmd = null;
-        appContextSetup = null;
         ApplicationContextUtil.resetApplicationContext();
     }
 
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/VMInfoCommandTest.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/VMInfoCommandTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -56,7 +56,6 @@
 
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
-import com.redhat.thermostat.common.cli.AppContextSetup;
 import com.redhat.thermostat.common.cli.ArgumentSpec;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.SimpleArgumentSpec;
@@ -87,7 +86,6 @@
 
     private VMInfoCommand cmd;
     private VmInfoDAO vmsDAO;
-    private AppContextSetup appContextSetup;
     private TestCommandContextFactory cmdCtxFactory;
     private VmRef vm;
 
@@ -103,13 +101,7 @@
     }
 
     private void setupCommandContextFactory() {
-        appContextSetup = mock(AppContextSetup.class);
-        cmdCtxFactory = new TestCommandContextFactory() {
-            @Override
-            protected AppContextSetup getAppContextSetup() {
-                return appContextSetup;
-            }
-        };
+        cmdCtxFactory = new TestCommandContextFactory();
     }
 
     private void setupDAOs() {
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java	Thu Sep 20 18:07:27 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java	Fri Sep 21 18:17:41 2012 +0200
@@ -60,7 +60,6 @@
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.appctx.ApplicationContext;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
-import com.redhat.thermostat.common.cli.AppContextSetup;
 import com.redhat.thermostat.common.cli.ArgumentSpec;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.SimpleArgumentSpec;
@@ -98,7 +97,6 @@
 
     private VMStatCommand cmd;
     private VmCpuStatDAO vmCpuStatDAO;
-    private AppContextSetup appContextSetup;
     private TestCommandContextFactory cmdCtxFactory;
     private VmMemoryStatDAO vmMemoryStatDAO;
     private TestTimerFactory timerFactory;
@@ -120,7 +118,6 @@
     public void tearDown() {
 
         vmCpuStatDAO = null;
-        appContextSetup = null;
         cmdCtxFactory = null;
         cmd = null;
         timerFactory = null;
@@ -128,13 +125,7 @@
     }
 
     private void setupCommandContextFactory() {
-        appContextSetup = mock(AppContextSetup.class);
-        cmdCtxFactory = new TestCommandContextFactory() {
-            @Override
-            protected AppContextSetup getAppContextSetup() {
-                return appContextSetup;
-            }
-        };
+        cmdCtxFactory = new TestCommandContextFactory();
     }
 
     private void setupDAOs() {