changeset 832:99a6f56a52b1

Merge
author Roman Kennke <rkennke@redhat.com>
date Mon, 10 Dec 2012 15:05:37 +0100
parents 6cd46e3563ce (current diff) 4a2c3aa5742e (diff)
children 941737272b79
files agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/StorageCommand.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/AgentApplicationTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/DBServiceTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/ThermostatServiceTest.java agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java client/memory-stats-panel/core/pom.xml client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryMeter.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsController.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsService.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsView.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsViewProvider.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/Payload.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/RangeModel.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/StatsModel.java client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/locale/LocaleResources.java client/memory-stats-panel/core/src/main/resources/com/redhat/thermostat/client/stats/memory/locale/strings.properties client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsControllerTest.java client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/PayloadTest.java client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/RangeModelTest.java client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/StatsModelTest.java client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/locale/TranslateTest.java client/memory-stats-panel/pom.xml client/memory-stats-panel/swing/pom.xml client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryGraphPanel.java client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsPanelActivator.java client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsViewImpl.java client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/SwingMemoryStatsViewProvider.java client/memory-stats-panel/swing/src/main/resources/com/redhat/thermostat/client/stats/memory/locale/strings.properties client/memory-stats-panel/swing/src/test/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsPanelActivatorTest.java common/command/src/main/java/com/redhat/thermostat/common/command/Response.java distribution/config/commands/gui.properties distribution/pom.xml pom.xml web/client/src/main/java/com/redhat/thermostat/web/client/Activator.java web/client/src/main/java/com/redhat/thermostat/web/client/WebCursor.java web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java web/client/src/test/java/com/redhat/thermostat/web/client/TestObj.java web/client/src/test/java/com/redhat/thermostat/web/client/WebStorageTest.java web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java
diffstat 135 files changed, 6430 insertions(+), 5121 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Dec 10 14:30:36 2012 +0100
+++ b/.hgignore	Mon Dec 10 15:05:37 2012 +0100
@@ -11,3 +11,5 @@
 target/*
 bin/*
 *~
+nbactions*.xml
+nb-configuration.xml
--- a/Makefile	Mon Dec 10 14:30:36 2012 +0100
+++ b/Makefile	Mon Dec 10 15:05:37 2012 +0100
@@ -11,7 +11,7 @@
 # Do not change anything below
 #
 REPO_FLAG       = -Dmaven.repo.local=$(REPO_LOC)
-GOAL            = integration-test
+GOAL            = package
 POM             = pom.xml
 
 ifeq ($(SKIP_TESTS),true)
--- a/agent/cli/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/cli/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -98,9 +98,6 @@
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Bundle-Activator>com.redhat.thermostat.agent.cli.impl.Activator</Bundle-Activator>
             <Bundle-SymbolicName>com.redhat.thermostat.agent.cli</Bundle-SymbolicName>
-            <Export-Package>
-              com.redhat.thermostat.agent.cli,
-            </Export-Package>
             <Private-Package>
               com.redhat.thermostat.agent.cli.impl,
               com.redhat.thermostat.agent.cli.impl.locale,
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +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.agent.cli;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-
-import sun.misc.Signal;
-import sun.misc.SignalHandler;
-
-import com.redhat.thermostat.agent.Agent;
-import com.redhat.thermostat.agent.command.ConfigurationServer;
-import com.redhat.thermostat.agent.config.AgentConfigsUtils;
-import com.redhat.thermostat.agent.config.AgentOptionParser;
-import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
-import com.redhat.thermostat.backend.BackendRegistry;
-import com.redhat.thermostat.backend.BackendService;
-import com.redhat.thermostat.common.Constants;
-import com.redhat.thermostat.common.LaunchException;
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.dao.DAOFactory;
-import com.redhat.thermostat.common.dao.DAOFactoryImpl;
-import com.redhat.thermostat.common.tools.BasicCommand;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.Connection;
-import com.redhat.thermostat.storage.core.StorageProvider;
-import com.redhat.thermostat.storage.core.StorageProviderUtil;
-import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
-import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-
-@SuppressWarnings("restriction")
-public final class AgentApplication extends BasicCommand {
-
-    private static final String NAME = "agent";
-
-    private final BundleContext bundleContext;
-    private final ConfigurationCreator configurationCreator;
-    private final DAOFactoryCreator daoFactoryCreator;
-
-    private AgentStartupConfiguration configuration;
-    private AgentOptionParser parser;
-
-    public AgentApplication(BundleContext bundleContext) {
-        this(bundleContext, new ConfigurationCreator(), new DAOFactoryCreator());
-    }
-
-    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DAOFactoryCreator daoFactoryCreator) {
-        this.bundleContext = bundleContext;
-        this.configurationCreator = configurationCreator;
-        this.daoFactoryCreator = daoFactoryCreator;
-    }
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-        parser = new AgentOptionParser(configuration, args);
-        parser.parse();
-    }
-
-    @Override
-    public AgentStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-    
-    private void runAgent(CommandContext ctx) {
-        long startTime = System.currentTimeMillis();
-        configuration.setStartTime(startTime);
-        
-        if (configuration.isDebugConsole()) {
-            LoggingUtils.useDevelConsole();
-        }
-        final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
-
-        final DAOFactory daoFactory = daoFactoryCreator.create(configuration);
-
-        Connection connection = daoFactory.getConnection();
-        ConnectionListener connectionListener = new ConnectionListener() {
-            @Override
-            public void changed(ConnectionStatus newStatus) {
-                switch (newStatus) {
-                case DISCONNECTED:
-                    logger.warning("Unexpected disconnect event.");
-                    break;
-                case CONNECTING:
-                    logger.fine("Connecting to storage.");
-                    break;
-                case CONNECTED:
-                    logger.fine("Connected to storage, registering storage as service");
-                    daoFactory.registerDAOsAndStorageAsOSGiServices();
-                    break;
-                case FAILED_TO_CONNECT:
-                    logger.warning("Could not connect to storage.");
-                    System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
-                default:
-                    logger.warning("Unfamiliar ConnectionStatus value");
-                }
-            }
-        };
-
-        connection.addListener(connectionListener);
-        connection.connect();
-        logger.fine("Connecting to storage...");
-
-        ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
-        final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
-        configServer.startListening(configuration.getConfigListenAddress());
-        
-        BackendRegistry backendRegistry = null;
-        try {
-            backendRegistry = new BackendRegistry(bundleContext);
-            
-        } catch (Exception e) {
-            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
-            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
-        }
-
-        final Agent agent = new Agent(backendRegistry, configuration, daoFactory);
-        try {
-            logger.fine("Starting agent.");
-            agent.start();
-            
-            bundleContext.registerService(BackendService.class, new BackendService(), null);
-            
-        } catch (LaunchException le) {
-            logger.log(Level.SEVERE,
-                    "Agent could not start, probably because a configured backend could not be activated.",
-                    le);
-            System.exit(Constants.EXIT_BACKEND_START_ERROR);
-        }
-        logger.fine("Agent started.");
-
-        ctx.getConsole().getOutput().println("Agent id: " + agent.getId());
-        ctx.getConsole().getOutput().println("agent started.");
-        logger.fine("Agent id: " + agent.getId());
-
-        final CountDownLatch shutdownLatch = new CountDownLatch(1);
-        SignalHandler handler = new CustomSignalHandler(agent, configServer, logger, shutdownLatch);
-        Signal.handle(new Signal("INT"), handler);
-        Signal.handle(new Signal("TERM"), handler);
-        try {
-            // Wait for either SIGINT or SIGTERM
-            shutdownLatch.await();
-            logger.fine("terminating agent cmd");
-        } catch (InterruptedException e) {
-            return;
-        }
-    }
-
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        try {
-            configuration = configurationCreator.create();
-
-            parseArguments(ctx.getArguments());
-            if (!parser.isHelp()) {
-                runAgent(ctx);
-            }
-        } catch (InvalidConfigurationException ex) {
-            throw new CommandException(ex);
-        }
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-    
-    // Does not need a reference of the enclosing type so lets declare this class static
-    private static class CustomSignalHandler implements SignalHandler {
-
-        private Agent agent;
-        private ConfigurationServer configServer;
-        private Logger logger;
-        private CountDownLatch shutdownLatch;
-        
-        CustomSignalHandler(Agent agent, ConfigurationServer configServer, Logger logger, CountDownLatch latch) {
-            this.agent = agent;
-            this.configServer = configServer;
-            this.logger = logger;
-            this.shutdownLatch = latch;
-        }
-        
-        @Override
-        public void handle(Signal arg0) {
-            configServer.stopListening();
-            try {
-                agent.stop();
-            } catch (Exception ex) {
-                // We don't want any exception to hold back the signal handler, otherwise
-                // there will be no way to actually stop Thermostat.
-                ex.printStackTrace();
-            }
-            logger.fine("Agent stopped.");       
-            shutdownLatch.countDown();
-        }
-        
-    }
-
-    static class ConfigurationCreator {
-        public AgentStartupConfiguration create() throws InvalidConfigurationException {
-            return AgentConfigsUtils.createAgentConfigs();
-        }
-    }
-
-    static class DAOFactoryCreator {
-        public DAOFactory create(AgentStartupConfiguration config) {
-            StorageProvider connProv = StorageProviderUtil.getStorageProvider(config);
-            final DAOFactory daoFactory = new DAOFactoryImpl(connProv);
-            return daoFactory;
-        }
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/ServiceCommand.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +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.agent.cli;
-
-import java.util.ArrayList;
-import java.util.List;
-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.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-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.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationState;
-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 SimpleCommand implements ActionListener<ApplicationState> {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private static final String NAME = "service";
-
-    private List<ActionListener<ApplicationState>> listeners;
-
-    private Semaphore agentBarrier = new Semaphore(0);
-
-    public ServiceCommand() {
-        listeners = new ArrayList<>();
-        listeners.add(this);
-    }
-
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        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() 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()) {
-            case START:
-                String dbUrl = storage.getConfiguration().getDBConnectionString();
-                Launcher launcher = getLauncher();
-                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
-                System.err.println(translator.localize(LocaleResources.STARTING_AGENT));
-                launcher.setArgs(agentArgs);
-                launcher.run();
-                agentBarrier.release();
-                break;
-            case FAIL:
-                System.err.println(translator.localize(LocaleResources.ERROR_STARTING_DB));
-                Object payload = actionEvent.getPayload();
-                if (payload instanceof StorageAlreadyRunningException) {
-                    System.err.println(translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
-                }
-                break;
-            }
-        }
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public boolean isAvailableInShell() {
-        return false;
-    }
-
-    @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	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,184 +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.agent.cli;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
-
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBOptionParser;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.config.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.tools.BasicCommand;
-
-public class StorageCommand extends BasicCommand {
-
-    private static final String NAME = "storage";
-
-    private DBStartupConfiguration configuration;
-    private DBOptionParser parser;
-    
-    private MongoProcessRunner runner;
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-    
-        this.configuration = new DBStartupConfiguration();
-        // configs, read everything that is in the configs
-        File propertyFile = ConfigUtils.getStorageConfigurationFile();
-        if (!propertyFile.exists()) {
-            throw new InvalidConfigurationException("can't access database configuration file " +
-                                                    propertyFile);
-        }
-        readAndSetProperties(propertyFile);
-        
-        parser = new DBOptionParser(configuration, args);
-        parser.parse();
-    }
-    
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-
-        try {
-            parseArgsAndRun(ctx);
-        } catch (InvalidConfigurationException e) {
-            throw new CommandException(e);
-        }
-    }
-
-    private void parseArgsAndRun(CommandContext ctx)
-            throws InvalidConfigurationException {
-        parseArguments(ctx.getArguments());
-
-        // dry run means we don't do anything at all
-        if (parser.isDryRun()) return;
-        
-        runner = createRunner();
-        try {
-            switch (parser.getAction()) {
-            case START:
-                startService();
-                break;
-            case STOP:
-                stopService();
-                break;
-             default:
-                break;
-            }
-            getNotifier().fireAction(ApplicationState.SUCCESS);
-        } catch (Exception e) {
-            getNotifier().fireAction(ApplicationState.FAIL, e);
-        }
-    }
-    
-    private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
-    
-        Properties properties = new Properties();
-        try {
-            properties.load(new FileInputStream(propertyFile));
-            
-        } catch (IOException e) {
-            throw new InvalidConfigurationException(e);
-        }
-        
-        if (properties.containsKey(DBConfig.PORT.name())) {
-            String port = (String) properties.get(DBConfig.PORT.name());
-            int localPort = Integer.parseInt(port);
-            configuration.setPort(localPort);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
-        }
-        
-        if (properties.containsKey(DBConfig.PROTOCOL.name())) {
-            String url = (String) properties.get(DBConfig.PROTOCOL.name());
-            configuration.setProtocol(url);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.PROTOCOL + " property missing");
-        }
-        
-        if (properties.containsKey(DBConfig.BIND.name())) {
-            String ip = (String) properties.get(DBConfig.BIND.name());
-            configuration.setBindIP(ip);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
-        }
-    }
-    
-    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        runner.startService();
-        getNotifier().fireAction(ApplicationState.START);
-    }
-    
-    
-    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        check();
-        runner.stopService();
-        getNotifier().fireAction(ApplicationState.STOP);
-    }
-    
-    MongoProcessRunner createRunner() {
-        return new MongoProcessRunner(configuration, parser.isQuiet());
-    }
-
-    private void check() throws InvalidConfigurationException {
-        if (!configuration.getDBPath().exists() ||
-            !configuration.getLogFile().getParentFile().exists() || 
-            !configuration.getPidFile().getParentFile().exists())
-        {
-            throw new InvalidConfigurationException("database directories do not exist...");
-        }
-    }
-
-    @Override
-    public DBStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java	Mon Dec 10 15:05:37 2012 +0100
@@ -36,7 +36,7 @@
 
 package com.redhat.thermostat.agent.cli.db;
 
-import com.redhat.thermostat.agent.cli.StorageCommand;
+import com.redhat.thermostat.agent.cli.impl.StorageCommand;
 
 /**
  * Set of configuration option that the {@link StorageCommand} understands.
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Mon Dec 10 15:05:37 2012 +0100
@@ -41,9 +41,6 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
-import com.redhat.thermostat.agent.cli.AgentApplication;
-import com.redhat.thermostat.agent.cli.StorageCommand;
-import com.redhat.thermostat.agent.cli.ServiceCommand;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,252 @@
+/*
+ * 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.agent.cli.impl;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+import com.redhat.thermostat.agent.Agent;
+import com.redhat.thermostat.agent.command.ConfigurationServer;
+import com.redhat.thermostat.agent.config.AgentConfigsUtils;
+import com.redhat.thermostat.agent.config.AgentOptionParser;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.backend.BackendRegistry;
+import com.redhat.thermostat.backend.BackendService;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.LaunchException;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.DAOFactoryImpl;
+import com.redhat.thermostat.common.tools.BasicCommand;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Connection;
+import com.redhat.thermostat.storage.core.StorageProvider;
+import com.redhat.thermostat.storage.core.StorageProviderUtil;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
+import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
+
+@SuppressWarnings("restriction")
+public final class AgentApplication extends BasicCommand {
+
+    private static final String NAME = "agent";
+
+    private final BundleContext bundleContext;
+    private final ConfigurationCreator configurationCreator;
+    private final DAOFactoryCreator daoFactoryCreator;
+
+    private AgentStartupConfiguration configuration;
+    private AgentOptionParser parser;
+
+    public AgentApplication(BundleContext bundleContext) {
+        this(bundleContext, new ConfigurationCreator(), new DAOFactoryCreator());
+    }
+
+    AgentApplication(BundleContext bundleContext, ConfigurationCreator configurationCreator, DAOFactoryCreator daoFactoryCreator) {
+        this.bundleContext = bundleContext;
+        this.configurationCreator = configurationCreator;
+        this.daoFactoryCreator = daoFactoryCreator;
+    }
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+        parser = new AgentOptionParser(configuration, args);
+        parser.parse();
+    }
+
+    @Override
+    public AgentStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+    
+    private void runAgent(CommandContext ctx) {
+        long startTime = System.currentTimeMillis();
+        configuration.setStartTime(startTime);
+        
+        if (configuration.isDebugConsole()) {
+            LoggingUtils.useDevelConsole();
+        }
+        final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
+
+        final DAOFactory daoFactory = daoFactoryCreator.create(configuration);
+
+        Connection connection = daoFactory.getConnection();
+        ConnectionListener connectionListener = new ConnectionListener() {
+            @Override
+            public void changed(ConnectionStatus newStatus) {
+                switch (newStatus) {
+                case DISCONNECTED:
+                    logger.warning("Unexpected disconnect event.");
+                    break;
+                case CONNECTING:
+                    logger.fine("Connecting to storage.");
+                    break;
+                case CONNECTED:
+                    logger.fine("Connected to storage, registering storage as service");
+                    daoFactory.registerDAOsAndStorageAsOSGiServices();
+                    break;
+                case FAILED_TO_CONNECT:
+                    logger.warning("Could not connect to storage.");
+                    System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
+                default:
+                    logger.warning("Unfamiliar ConnectionStatus value");
+                }
+            }
+        };
+
+        connection.addListener(connectionListener);
+        connection.connect();
+        logger.fine("Connecting to storage...");
+
+        ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
+        final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
+        configServer.startListening(configuration.getConfigListenAddress());
+        
+        BackendRegistry backendRegistry = null;
+        try {
+            backendRegistry = new BackendRegistry(bundleContext);
+            
+        } catch (Exception e) {
+            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
+            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
+        }
+
+        final Agent agent = new Agent(backendRegistry, configuration, daoFactory);
+        try {
+            logger.fine("Starting agent.");
+            agent.start();
+            
+            bundleContext.registerService(BackendService.class, new BackendService(), null);
+            
+        } catch (LaunchException le) {
+            logger.log(Level.SEVERE,
+                    "Agent could not start, probably because a configured backend could not be activated.",
+                    le);
+            System.exit(Constants.EXIT_BACKEND_START_ERROR);
+        }
+        logger.fine("Agent started.");
+
+        ctx.getConsole().getOutput().println("Agent id: " + agent.getId());
+        ctx.getConsole().getOutput().println("agent started.");
+        logger.fine("Agent id: " + agent.getId());
+
+        final CountDownLatch shutdownLatch = new CountDownLatch(1);
+        SignalHandler handler = new CustomSignalHandler(agent, configServer, logger, shutdownLatch);
+        Signal.handle(new Signal("INT"), handler);
+        Signal.handle(new Signal("TERM"), handler);
+        try {
+            // Wait for either SIGINT or SIGTERM
+            shutdownLatch.await();
+            logger.fine("terminating agent cmd");
+        } catch (InterruptedException e) {
+            return;
+        }
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        try {
+            configuration = configurationCreator.create();
+
+            parseArguments(ctx.getArguments());
+            if (!parser.isHelp()) {
+                runAgent(ctx);
+            }
+        } catch (InvalidConfigurationException ex) {
+            throw new CommandException(ex);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+    
+    // Does not need a reference of the enclosing type so lets declare this class static
+    private static class CustomSignalHandler implements SignalHandler {
+
+        private Agent agent;
+        private ConfigurationServer configServer;
+        private Logger logger;
+        private CountDownLatch shutdownLatch;
+        
+        CustomSignalHandler(Agent agent, ConfigurationServer configServer, Logger logger, CountDownLatch latch) {
+            this.agent = agent;
+            this.configServer = configServer;
+            this.logger = logger;
+            this.shutdownLatch = latch;
+        }
+        
+        @Override
+        public void handle(Signal arg0) {
+            configServer.stopListening();
+            try {
+                agent.stop();
+            } catch (Exception ex) {
+                // We don't want any exception to hold back the signal handler, otherwise
+                // there will be no way to actually stop Thermostat.
+                ex.printStackTrace();
+            }
+            logger.fine("Agent stopped.");       
+            shutdownLatch.countDown();
+        }
+        
+    }
+
+    static class ConfigurationCreator {
+        public AgentStartupConfiguration create() throws InvalidConfigurationException {
+            return AgentConfigsUtils.createAgentConfigs();
+        }
+    }
+
+    static class DAOFactoryCreator {
+        public DAOFactory create(AgentStartupConfiguration config) {
+            StorageProvider connProv = StorageProviderUtil.getStorageProvider(config);
+            final DAOFactory daoFactory = new DAOFactoryImpl(connProv);
+            return daoFactory;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,135 @@
+/*
+ * 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.agent.cli.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+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.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+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.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+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 SimpleCommand implements ActionListener<ApplicationState> {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private static final String NAME = "service";
+
+    private List<ActionListener<ApplicationState>> listeners;
+
+    private Semaphore agentBarrier = new Semaphore(0);
+
+    public ServiceCommand() {
+        listeners = new ArrayList<>();
+        listeners.add(this);
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        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() 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()) {
+            case START:
+                String dbUrl = storage.getConfiguration().getDBConnectionString();
+                Launcher launcher = getLauncher();
+                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
+                System.err.println(translator.localize(LocaleResources.STARTING_AGENT));
+                launcher.setArgs(agentArgs);
+                launcher.run();
+                agentBarrier.release();
+                break;
+            case FAIL:
+                System.err.println(translator.localize(LocaleResources.ERROR_STARTING_DB));
+                Object payload = actionEvent.getPayload();
+                if (payload instanceof StorageAlreadyRunningException) {
+                    System.err.println(translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING));
+                }
+                break;
+            }
+        }
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public boolean isAvailableInShell() {
+        return false;
+    }
+
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+    private Launcher getLauncher() {
+        OSGIUtils osgi = OSGIUtils.getInstance();
+        return osgi.getService(Launcher.class);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,184 @@
+/*
+ * 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.agent.cli.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.agent.cli.db.DBConfig;
+import com.redhat.thermostat.agent.cli.db.DBOptionParser;
+import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
+import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.config.ConfigUtils;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.tools.BasicCommand;
+
+public class StorageCommand extends BasicCommand {
+
+    private static final String NAME = "storage";
+
+    private DBStartupConfiguration configuration;
+    private DBOptionParser parser;
+    
+    private MongoProcessRunner runner;
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+    
+        this.configuration = new DBStartupConfiguration();
+        // configs, read everything that is in the configs
+        File propertyFile = ConfigUtils.getStorageConfigurationFile();
+        if (!propertyFile.exists()) {
+            throw new InvalidConfigurationException("can't access database configuration file " +
+                                                    propertyFile);
+        }
+        readAndSetProperties(propertyFile);
+        
+        parser = new DBOptionParser(configuration, args);
+        parser.parse();
+    }
+    
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+
+        try {
+            parseArgsAndRun(ctx);
+        } catch (InvalidConfigurationException e) {
+            throw new CommandException(e);
+        }
+    }
+
+    private void parseArgsAndRun(CommandContext ctx)
+            throws InvalidConfigurationException {
+        parseArguments(ctx.getArguments());
+
+        // dry run means we don't do anything at all
+        if (parser.isDryRun()) return;
+        
+        runner = createRunner();
+        try {
+            switch (parser.getAction()) {
+            case START:
+                startService();
+                break;
+            case STOP:
+                stopService();
+                break;
+             default:
+                break;
+            }
+            getNotifier().fireAction(ApplicationState.SUCCESS);
+        } catch (Exception e) {
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        }
+    }
+    
+    private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
+    
+        Properties properties = new Properties();
+        try {
+            properties.load(new FileInputStream(propertyFile));
+            
+        } catch (IOException e) {
+            throw new InvalidConfigurationException(e);
+        }
+        
+        if (properties.containsKey(DBConfig.PORT.name())) {
+            String port = (String) properties.get(DBConfig.PORT.name());
+            int localPort = Integer.parseInt(port);
+            configuration.setPort(localPort);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.PROTOCOL.name())) {
+            String url = (String) properties.get(DBConfig.PROTOCOL.name());
+            configuration.setProtocol(url);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PROTOCOL + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.BIND.name())) {
+            String ip = (String) properties.get(DBConfig.BIND.name());
+            configuration.setBindIP(ip);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
+        }
+    }
+    
+    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        runner.startService();
+        getNotifier().fireAction(ApplicationState.START);
+    }
+    
+    
+    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        check();
+        runner.stopService();
+        getNotifier().fireAction(ApplicationState.STOP);
+    }
+    
+    MongoProcessRunner createRunner() {
+        return new MongoProcessRunner(configuration, parser.isQuiet());
+    }
+
+    private void check() throws InvalidConfigurationException {
+        if (!configuration.getDBPath().exists() ||
+            !configuration.getLogFile().getParentFile().exists() || 
+            !configuration.getPidFile().getParentFile().exists())
+        {
+            throw new InvalidConfigurationException("database directories do not exist...");
+        }
+    }
+
+    @Override
+    public DBStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/AgentApplicationTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +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.agent.cli;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.redhat.thermostat.agent.cli.AgentApplication.ConfigurationCreator;
-import com.redhat.thermostat.agent.cli.AgentApplication.DAOFactoryCreator;
-import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.dao.DAOFactory;
-import com.redhat.thermostat.storage.core.Connection;
-import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
-import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.test.StubBundleContext;
-
-public class AgentApplicationTest {
-
-    // TODO: Test i18nized versions when they come.
-
-    private StubBundleContext context;
-    private DAOFactory daoFactory;
-    private Connection connection;
-
-    private AgentApplication agent;
-
-    private ArgumentCaptor<ConnectionListener> listenerCaptor;
-    
-    @Before
-    public void setUp() throws InvalidConfigurationException {
-        
-        context = new StubBundleContext();
-        
-        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
-        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
-
-        ConfigurationCreator configCreator = mock(ConfigurationCreator.class);
-        when(configCreator.create()).thenReturn(config);
-
-        listenerCaptor = ArgumentCaptor.forClass(ConnectionListener.class);
-
-        connection = mock(Connection.class);
-        doNothing().when(connection).addListener(listenerCaptor.capture());
-
-        daoFactory = mock(DAOFactory.class);
-        when(daoFactory.getConnection()).thenReturn(connection);
-
-        DAOFactoryCreator daoCreator = mock(DAOFactoryCreator.class);
-        when(daoCreator.create(config)).thenReturn(daoFactory);
-
-        agent = new AgentApplication(context, configCreator, daoCreator);
-    }
-
-    @After
-    public void tearDown() {
-        agent = null;
-    }
-
-    @Test
-    public void testName() {
-        String name = agent.getName();
-        assertEquals("agent", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        assertNotNull(agent.getDescription());
-        assertNotNull(agent.getUsage());
-    }
-
-    @Test
-    public void testAgentStartupRegistersDAOs() throws CommandException {
-
-        doThrow(new ThatsAllThatWeCareAbout()).when(connection).connect();
-
-        Arguments args = mock(Arguments.class);
-        CommandContext commandContext = mock(CommandContext.class);
-
-        when(commandContext.getArguments()).thenReturn(args);
-
-        try {
-            agent.run(commandContext);
-            fail("not supposed to get here");
-        } catch (ThatsAllThatWeCareAbout done) {
-            // agent.run() is so convoluted that we just want to test a part of
-            // it
-        }
-
-        ConnectionListener listener = listenerCaptor.getValue();
-        listener.changed(ConnectionStatus.CONNECTED);
-
-        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
-    }
-
-    // FIXME test the rest of AgentApplication
-
-    private static class ThatsAllThatWeCareAbout extends RuntimeException {
-
-    }
-}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/DBServiceTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +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.agent.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.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Properties;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-
-import junit.framework.Assert;
-
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionGroup;
-import org.apache.commons.cli.Options;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.StorageCommand;
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-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.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-
-public class DBServiceTest {
-    
-    private static final String PORT = "27518";
-    private static final String BIND = "127.0.0.1";
-    private static final String PROTOCOL = "mongodb";
-    private static final String DB = "storage/db";
-
-    private String tmpDir;
-    
-    @Before
-    public void setup() {
-        // need to create a dummy config file for the test
-        try {
-            Random random = new Random();
-            
-            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
-                     Math.abs(random.nextInt()) + File.separatorChar;
-            
-            System.setProperty("THERMOSTAT_HOME", tmpDir);
-            File base = new File(tmpDir + "storage");
-            base.mkdirs();
-                        
-            File tmpConfigs = new File(base, "db.properties");
-            
-            new File(base, "run").mkdirs();
-            new File(base, "logs").mkdirs();
-            new File(base, "db").mkdirs();
-            
-            Properties props = new Properties();
-            
-            props.setProperty(DBConfig.BIND.name(), BIND);
-            props.setProperty(DBConfig.PORT.name(), PORT);
-            props.setProperty(DBConfig.PROTOCOL.name(), PROTOCOL);
-
-            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
-            
-        } catch (IOException e) {
-            Assert.fail("cannot setup tests: " + e);
-        }
-    }
-    
-    @Test
-    public void testConfig() throws CommandException {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", null);
-        args.addArgument("start", null);
-        args.addArgument("dryRun", null);
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-
-        StorageCommand service = new StorageCommand() {
-            @Override
-            MongoProcessRunner createRunner() {
-                throw new AssertionError("dry run should never create an actual runner");
-            }
-        };
-
-        service.run(ctx);
-        
-        DBStartupConfiguration conf = service.getConfiguration();
-        
-        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
-        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
-        Assert.assertEquals(PROTOCOL, conf.getProtocol());
-    }
-    
-    private StorageCommand prepareService(boolean startSuccess) throws IOException,
-            InterruptedException, InvalidConfigurationException, ApplicationException
-    {
-        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
-        if (!startSuccess) {
-           doThrow(new ApplicationException("mock exception")).when(runner).startService();
-        }
-        
-        // TODO: stop not tested yet, but be sure it's not called from the code
-        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
-        
-        StorageCommand service = new StorageCommand() {
-            @Override
-            MongoProcessRunner createRunner() {
-                return runner;
-            }
-        };
-        
-        return service;
-    }
-    
-    @Test
-    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
-    {
-        StorageCommand service = prepareService(true);
-        
-        final CountDownLatch latch = new CountDownLatch(2);
-        
-        final boolean[] result = new boolean[2];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = false;
-                    latch.countDown();
-                    latch.countDown();
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = true;
-                    latch.countDown();
-                    break;
-
-                case START:
-                    result[1] = true;
-                    latch.countDown();
-                    break;
-                }
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-        Assert.assertTrue(result[1]);
-    }
-    
-    @Test
-    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
-    {
-        StorageCommand service = prepareService(false);
-        
-        final CountDownLatch latch = new CountDownLatch(1);
-        final boolean[] result = new boolean[1];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = true;
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = false;
-                    break;
-                }
-                latch.countDown();
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-    }
-
-    private CommandContext prepareContext() {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", "--quiet");
-        args.addArgument("start", "--start");
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-        return ctx;
-    }
-
-    @Test
-    public void testName() {
-        StorageCommand dbService = new StorageCommand();
-        String name = dbService.getName();
-        assertEquals("storage", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        StorageCommand dbService = new StorageCommand();
-        assertNotNull(dbService.getDescription());
-        assertNotNull(dbService.getUsage());
-    }
-
-    @Ignore
-    @Test
-    public void testOptions() {
-        StorageCommand dbService = new StorageCommand();
-        Options options = dbService.getOptions();
-        assertNotNull(options);
-        assertEquals(4, options.getOptions().size());
-
-        assertTrue(options.hasOption("dryRun"));
-        Option dry = options.getOption("dryRun");
-        assertEquals("d", dry.getOpt());
-        assertEquals("run the service in dry run mode", dry.getDescription());
-        assertFalse(dry.isRequired());
-        assertFalse(dry.hasArg());
-
-        assertTrue(options.hasOption("start"));
-        Option start = options.getOption("start");
-        assertEquals("start the database", start.getDescription());
-        assertFalse(start.isRequired());
-        assertFalse(start.hasArg());
-
-        assertTrue(options.hasOption("stop"));
-        Option stop = options.getOption("stop");
-        assertEquals("stop the database", stop.getDescription());
-        assertFalse(stop.isRequired());
-        assertFalse(stop.hasArg());
-
-        assertTrue(options.hasOption("quiet"));
-        Option quiet = options.getOption("quiet");
-        assertEquals("q", quiet.getOpt());
-        assertEquals("don't produce any output", quiet.getDescription());
-        assertFalse(quiet.isRequired());
-        assertFalse(quiet.hasArg());
-
-        OptionGroup startStop = options.getOptionGroup(start);
-        assertTrue(startStop.isRequired());
-        @SuppressWarnings("unchecked")
-        Collection<Option> groupOpts = startStop.getOptions();
-        assertEquals(2, groupOpts.size());
-        assertTrue(groupOpts.contains(start));
-        assertTrue(groupOpts.contains(stop));
-    }
-}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/ThermostatServiceTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +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.agent.cli;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.apache.commons.cli.Options;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.ServiceCommand;
-
-public class ThermostatServiceTest {
-
-    private ServiceCommand thermostatService;
-
-    @Before
-    public void setUp() {
-        thermostatService = new ServiceCommand();
-    }
-
-    @After
-    public void tearDown() {
-        thermostatService = null;
-    }
-
-    @Test
-    public void testName() {
-        String name = thermostatService.getName();
-        assertEquals("service", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        assertNotNull(thermostatService.getDescription());
-        assertNotNull(thermostatService.getUsage());
-    }
-
-    @Test
-    public void testOptions() {
-        Options options = thermostatService.getOptions();
-        assertNotNull(options);
-        assertEquals(0, options.getOptions().size());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,66 @@
+/*
+ * 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class ActivatorTest {
+
+    @Test
+    public void verifyActivatorRegistersCommands() throws Exception {
+
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(bundleContext);
+
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), AgentApplication.class));
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), ServiceCommand.class));
+        assertTrue(bundleContext.isServiceRegistered(Command.class.getName(), StorageCommand.class));
+
+        activator.stop(bundleContext);
+
+        assertEquals(0, bundleContext.getAllServices().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,150 @@
+/*
+ * 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.agent.cli.impl.AgentApplication;
+import com.redhat.thermostat.agent.cli.impl.AgentApplication.ConfigurationCreator;
+import com.redhat.thermostat.agent.cli.impl.AgentApplication.DAOFactoryCreator;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.storage.core.Connection;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
+import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class AgentApplicationTest {
+
+    // TODO: Test i18nized versions when they come.
+
+    private StubBundleContext context;
+    private DAOFactory daoFactory;
+    private Connection connection;
+
+    private AgentApplication agent;
+
+    private ArgumentCaptor<ConnectionListener> listenerCaptor;
+    
+    @Before
+    public void setUp() throws InvalidConfigurationException {
+        
+        context = new StubBundleContext();
+        
+        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
+        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
+
+        ConfigurationCreator configCreator = mock(ConfigurationCreator.class);
+        when(configCreator.create()).thenReturn(config);
+
+        listenerCaptor = ArgumentCaptor.forClass(ConnectionListener.class);
+
+        connection = mock(Connection.class);
+        doNothing().when(connection).addListener(listenerCaptor.capture());
+
+        daoFactory = mock(DAOFactory.class);
+        when(daoFactory.getConnection()).thenReturn(connection);
+
+        DAOFactoryCreator daoCreator = mock(DAOFactoryCreator.class);
+        when(daoCreator.create(config)).thenReturn(daoFactory);
+
+        agent = new AgentApplication(context, configCreator, daoCreator);
+    }
+
+    @After
+    public void tearDown() {
+        agent = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = agent.getName();
+        assertEquals("agent", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        assertNotNull(agent.getDescription());
+        assertNotNull(agent.getUsage());
+    }
+
+    @Test
+    public void testAgentStartupRegistersDAOs() throws CommandException {
+
+        doThrow(new ThatsAllThatWeCareAbout()).when(connection).connect();
+
+        Arguments args = mock(Arguments.class);
+        CommandContext commandContext = mock(CommandContext.class);
+
+        when(commandContext.getArguments()).thenReturn(args);
+
+        try {
+            agent.run(commandContext);
+            fail("not supposed to get here");
+        } catch (ThatsAllThatWeCareAbout done) {
+            // agent.run() is so convoluted that we just want to test a part of
+            // it
+        }
+
+        ConnectionListener listener = listenerCaptor.getValue();
+        listener.changed(ConnectionStatus.CONNECTED);
+
+        verify(daoFactory).registerDAOsAndStorageAsOSGiServices();
+    }
+
+    // FIXME test the rest of AgentApplication
+
+    private static class ThatsAllThatWeCareAbout extends RuntimeException {
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,81 @@
+/*
+ * 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.agent.cli.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.commons.cli.Options;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.impl.ServiceCommand;
+
+public class ServiceCommandTest {
+
+    private ServiceCommand thermostatService;
+
+    @Before
+    public void setUp() {
+        thermostatService = new ServiceCommand();
+    }
+
+    @After
+    public void tearDown() {
+        thermostatService = null;
+    }
+
+    @Test
+    public void testName() {
+        String name = thermostatService.getName();
+        assertEquals("service", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        assertNotNull(thermostatService.getDescription());
+        assertNotNull(thermostatService.getUsage());
+    }
+
+    @Test
+    public void testOptions() {
+        Options options = thermostatService.getOptions();
+        assertNotNull(options);
+        assertEquals(0, options.getOptions().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,296 @@
+/*
+ * 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.agent.cli.impl;
+
+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.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Properties;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.Assert;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.db.DBConfig;
+import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
+import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
+import com.redhat.thermostat.agent.cli.impl.StorageCommand;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+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.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
+public class StorageCommandTest {
+    
+    private static final String PORT = "27518";
+    private static final String BIND = "127.0.0.1";
+    private static final String PROTOCOL = "mongodb";
+    private static final String DB = "storage/db";
+
+    private String tmpDir;
+    
+    @Before
+    public void setup() {
+        // need to create a dummy config file for the test
+        try {
+            Random random = new Random();
+            
+            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
+                     Math.abs(random.nextInt()) + File.separatorChar;
+            
+            System.setProperty("THERMOSTAT_HOME", tmpDir);
+            File base = new File(tmpDir + "storage");
+            base.mkdirs();
+                        
+            File tmpConfigs = new File(base, "db.properties");
+            
+            new File(base, "run").mkdirs();
+            new File(base, "logs").mkdirs();
+            new File(base, "db").mkdirs();
+            
+            Properties props = new Properties();
+            
+            props.setProperty(DBConfig.BIND.name(), BIND);
+            props.setProperty(DBConfig.PORT.name(), PORT);
+            props.setProperty(DBConfig.PROTOCOL.name(), PROTOCOL);
+
+            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
+            
+        } catch (IOException e) {
+            Assert.fail("cannot setup tests: " + e);
+        }
+    }
+    
+    @Test
+    public void testConfig() throws CommandException {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", null);
+        args.addArgument("start", null);
+        args.addArgument("dryRun", null);
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+
+        StorageCommand service = new StorageCommand() {
+            @Override
+            MongoProcessRunner createRunner() {
+                throw new AssertionError("dry run should never create an actual runner");
+            }
+        };
+
+        service.run(ctx);
+        
+        DBStartupConfiguration conf = service.getConfiguration();
+        
+        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
+        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
+        Assert.assertEquals(PROTOCOL, conf.getProtocol());
+    }
+    
+    private StorageCommand prepareService(boolean startSuccess) throws IOException,
+            InterruptedException, InvalidConfigurationException, ApplicationException
+    {
+        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
+        if (!startSuccess) {
+           doThrow(new ApplicationException("mock exception")).when(runner).startService();
+        }
+        
+        // TODO: stop not tested yet, but be sure it's not called from the code
+        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
+        
+        StorageCommand service = new StorageCommand() {
+            @Override
+            MongoProcessRunner createRunner() {
+                return runner;
+            }
+        };
+        
+        return service;
+    }
+    
+    @Test
+    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
+    {
+        StorageCommand service = prepareService(true);
+        
+        final CountDownLatch latch = new CountDownLatch(2);
+        
+        final boolean[] result = new boolean[2];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = false;
+                    latch.countDown();
+                    latch.countDown();
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = true;
+                    latch.countDown();
+                    break;
+
+                case START:
+                    result[1] = true;
+                    latch.countDown();
+                    break;
+                }
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+        Assert.assertTrue(result[1]);
+    }
+    
+    @Test
+    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
+    {
+        StorageCommand service = prepareService(false);
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = true;
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = false;
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+    }
+
+    private CommandContext prepareContext() {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", "--quiet");
+        args.addArgument("start", "--start");
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+        return ctx;
+    }
+
+    @Test
+    public void testName() {
+        StorageCommand dbService = new StorageCommand();
+        String name = dbService.getName();
+        assertEquals("storage", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        StorageCommand dbService = new StorageCommand();
+        assertNotNull(dbService.getDescription());
+        assertNotNull(dbService.getUsage());
+    }
+
+    @Ignore
+    @Test
+    public void testOptions() {
+        StorageCommand dbService = new StorageCommand();
+        Options options = dbService.getOptions();
+        assertNotNull(options);
+        assertEquals(4, options.getOptions().size());
+
+        assertTrue(options.hasOption("dryRun"));
+        Option dry = options.getOption("dryRun");
+        assertEquals("d", dry.getOpt());
+        assertEquals("run the service in dry run mode", dry.getDescription());
+        assertFalse(dry.isRequired());
+        assertFalse(dry.hasArg());
+
+        assertTrue(options.hasOption("start"));
+        Option start = options.getOption("start");
+        assertEquals("start the database", start.getDescription());
+        assertFalse(start.isRequired());
+        assertFalse(start.hasArg());
+
+        assertTrue(options.hasOption("stop"));
+        Option stop = options.getOption("stop");
+        assertEquals("stop the database", stop.getDescription());
+        assertFalse(stop.isRequired());
+        assertFalse(stop.hasArg());
+
+        assertTrue(options.hasOption("quiet"));
+        Option quiet = options.getOption("quiet");
+        assertEquals("q", quiet.getOpt());
+        assertEquals("don't produce any output", quiet.getDescription());
+        assertFalse(quiet.isRequired());
+        assertFalse(quiet.hasArg());
+
+        OptionGroup startStop = options.getOptionGroup(start);
+        assertTrue(startStop.isRequired());
+        @SuppressWarnings("unchecked")
+        Collection<Option> groupOpts = startStop.getOptions();
+        assertEquals(2, groupOpts.size());
+        assertTrue(groupOpts.contains(start));
+        assertTrue(groupOpts.contains(stop));
+    }
+}
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/PingReceiver.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/PingReceiver.java	Mon Dec 10 15:05:37 2012 +0100
@@ -45,7 +45,7 @@
 
     @Override
     public Response receive(Request request) {
-        return new Response(ResponseType.PONG);
+        return new Response(ResponseType.OK);
     }
 
 }
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/internal/ServerHandler.java	Mon Dec 10 15:05:37 2012 +0100
@@ -125,14 +125,4 @@
         e.getChannel().close();
     }
 
-    private Response doRequest(Request request) {
-        Response response = new Response(ResponseType.ERROR);
-        switch ((RequestType) request.getType()) {
-        case RESPONSE_EXPECTED:
-            response = new Response(ResponseType.PONG);
-            break;
-        default:
-        }
-        return response;
-    }
 }
--- a/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ResponseEncoderTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/command/src/test/java/com/redhat/thermostat/agent/command/internal/ResponseEncoderTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -68,7 +68,7 @@
 
     @Before
     public void setUp() {
-        Response r = new Response(ResponseType.PONG);
+        Response r = new Response(ResponseType.OK);
         e = mock(MessageEvent.class);
         when(e.getMessage()).thenReturn(r);
         when(e.getFuture()).thenReturn(null);
@@ -89,10 +89,10 @@
 
         ChannelBuffer buf = (ChannelBuffer) argument.getValue();
         int messageLength = buf.readInt();
-        assertEquals(4, messageLength);
+        assertEquals(2, messageLength);
         ByteBuffer bbuf = ByteBuffer.allocate(buf.readableBytes());
         buf.readBytes(bbuf);
         String message = new String(bbuf.array());
-        assertEquals("PONG", message);
+        assertEquals("OK", message);
     }
 }
--- a/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiver.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiver.java	Mon Dec 10 15:05:37 2012 +0100
@@ -84,10 +84,10 @@
             
         } catch (IOException e) {
             log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e);
-            return new Response(ResponseType.EXCEPTION);
+            return new Response(ResponseType.ERROR);
         } catch (HeapDumpException e) {
             log.log(Level.SEVERE, "Unexpected problem while writing heap dump", e);
-            return new Response(ResponseType.EXCEPTION);
+            return new Response(ResponseType.ERROR);
         }
         return new Response(ResponseType.OK);
     }
--- a/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiverTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiverTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -127,7 +127,7 @@
         doThrow(new HeapDumpException()).when(jmapDumper).dumpHeap(anyString(), anyString());
         Response response = receiver.receive(request);
 
-        assertEquals(ResponseType.EXCEPTION, response.getType());
+        assertEquals(ResponseType.ERROR, response.getType());
         
     }
 
@@ -137,7 +137,7 @@
 
         Response response = receiver.receive(request);
 
-        assertEquals(ResponseType.EXCEPTION, response.getType());
+        assertEquals(ResponseType.ERROR, response.getType());
         
     }
 }
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/cli/PingCommand.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/cli/PingCommand.java	Mon Dec 10 15:05:37 2012 +0100
@@ -79,14 +79,10 @@
             case ERROR:
                 out.println(translator.localize(LocaleResources.COMMAND_PING_RESPONSE_ERROR, request.getTarget().toString()));
                 break;
-            case PONG:
             case OK:
             case NOOP:
                 out.println(translator.localize(LocaleResources.COMMAND_PING_RESPONSE_OK, request.getTarget().toString()));
                 break;
-            case EXCEPTION:
-                out.println(translator.localize(LocaleResources.COMMAND_PING_RESPONSE_EXCEPTION));
-                break;
             case NOK:
                 out.println(translator.localize(LocaleResources.COMMAND_PING_RESPONSE_REFUSED));
                 break;
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/LocaleResources.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/LocaleResources.java	Mon Dec 10 15:05:37 2012 +0100
@@ -49,7 +49,6 @@
     COMMAND_PING_NO_AGENT_INFO_DAO,
 
     COMMAND_PING_RESPONSE_ERROR,
-    COMMAND_PING_RESPONSE_EXCEPTION,
     COMMAND_PING_RESPONSE_OK,
     COMMAND_PING_RESPONSE_REFUSED,
     COMMAND_PING_RESPONSE_UNKNOWN,
--- a/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/command/src/main/java/com/redhat/thermostat/client/command/internal/ResponseHandler.java	Mon Dec 10 15:05:37 2012 +0100
@@ -72,7 +72,7 @@
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
         // TODO when response has support for parameters, provide the exception as well.
-        Response response = new Response(ResponseType.EXCEPTION);
+        Response response = new Response(ResponseType.ERROR);
         notifyListeners(response);
     }
 
--- a/client/command/src/main/resources/com/redhat/thermostat/client/command/internal/strings.properties	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/command/src/main/resources/com/redhat/thermostat/client/command/internal/strings.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -6,7 +6,6 @@
 COMMAND_PING_NO_AGENT_INFO_DAO = Unable to access agent information: service not available
 
 COMMAND_PING_RESPONSE_ERROR = Error received from: {0}
-COMMAND_PING_RESPONSE_EXCEPTION = The tubes, they are probably broken.
 COMMAND_PING_RESPONSE_OK = Response received from: {0}
 COMMAND_PING_RESPONSE_REFUSED = The server refused to PONG our PING?
 COMMAND_PING_RESPONSE_UNKNOWN = Unknown result from ping command
\ No newline at end of file
--- a/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java	Mon Dec 10 15:05:37 2012 +0100
@@ -163,6 +163,14 @@
                         public void run() {
                             view.notifyHeapDumpComplete();
                         }
+                    }, new Runnable() {
+                        @Override
+                        public void run() {
+                            view.displayWarning(translator.localize(
+                                    LocaleResources.HEAP_DUMP_ERROR,
+                                    ref.getAgent().getAgentId(),
+                                    ref.getIdString()));
+                        }
                     });
                     
                     break;
--- a/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/HeapView.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/HeapView.java	Mon Dec 10 15:05:37 2012 +0100
@@ -78,4 +78,6 @@
 
     public abstract void updateHeapDumpList(List<HeapDump> heapDumps);
 
+    public abstract void displayWarning(String string);
+
 }
--- a/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/LocaleResources.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/LocaleResources.java	Mon Dec 10 15:05:37 2012 +0100
@@ -57,6 +57,7 @@
     HEAP_ID_NOT_FOUND,
     HEAP_ID_REQUIRED,
     SEARCH_TERM_REQUIRED,
+    HEAP_DUMP_ERROR,
 
     COMMAND_HEAP_DUMP_DONE,
 
--- a/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java	Mon Dec 10 15:05:37 2012 +0100
@@ -71,28 +71,33 @@
     }
 
     @Override
-    public void run(CommandContext ctx) throws CommandException {
+    public void run(final CommandContext ctx) throws CommandException {
         HostVMArguments args = new HostVMArguments(ctx.getArguments());
 
         final Semaphore s = new Semaphore(0);
-        Runnable r = new Runnable() {
-            
+        Runnable successHandler = new Runnable() {
             @Override
             public void run() {
+                ctx.getConsole().getOutput().println(translator.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE));
                 s.release();
             }
         };
+        Runnable errorHandler = new Runnable() {
+            public void run() {
+                ctx.getConsole().getError().println(translator.localize(LocaleResources.HEAP_DUMP_ERROR));
+                s.release();
+            }
+        };
+
         AgentInfoDAO service = serviceProvider.getService(AgentInfoDAO.class);
         if (service == null) {
             throw new CommandException("Unable to access agent information");
         }
-        implementation.execute(service, args.getVM(), r);
+        implementation.execute(service, args.getVM(), successHandler, errorHandler);
         serviceProvider.ungetService(AgentInfoDAO.class, service);
 
         try {
             s.acquire();
-            ctx.getConsole().getOutput().print(translator.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE));
-            ctx.getConsole().getOutput().print("\n");
         } catch (InterruptedException ex) {
             // Nothing to do here, just return ASAP.
         }
--- a/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java	Mon Dec 10 15:05:37 2012 +0100
@@ -43,6 +43,7 @@
 import com.redhat.thermostat.common.command.Request.RequestType;
 import com.redhat.thermostat.common.command.RequestResponseListener;
 import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
 import com.redhat.thermostat.common.dao.AgentInfoDAO;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
@@ -55,20 +56,30 @@
 
     private class HeapDumpListener implements RequestResponseListener {
 
-        private Runnable heapDumpCompleteAction;
+        private Runnable successAction;
+        private Runnable failureAction;
 
-        private HeapDumpListener(Runnable heapDumpCompleteAction) {
-            this.heapDumpCompleteAction = heapDumpCompleteAction;
+        private HeapDumpListener(Runnable heapDumpSuccessfulAction, Runnable heapDumpFailureAction) {
+            this.successAction = heapDumpSuccessfulAction;
+            this.failureAction = heapDumpFailureAction;
         }
 
         @Override
         public void fireComplete(Request request, Response response) {
-            heapDumpCompleteAction.run();
+            if (response.getType() == ResponseType.ERROR) {
+                if (failureAction != null) {
+                    failureAction.run();
+                }
+            } else {
+                if (successAction != null) {
+                    successAction.run();
+                }
+            }
         }
-        
+
     }
 
-    public void execute(AgentInfoDAO agentInfoDAO, VmRef reference, Runnable heapDumpCompleteAction) {
+    public void execute(AgentInfoDAO agentInfoDAO, VmRef reference, Runnable heapDumpSuccessAction, Runnable heapDumpFailureAction) {
 
         HostRef targetHostRef = reference.getAgent();
         String address = agentInfoDAO.getAgentInformation(targetHostRef).getConfigListenAddress();
@@ -78,7 +89,7 @@
         Request req = new Request(RequestType.RESPONSE_EXPECTED, target);
         req.setReceiver(RECEIVER_CLASS_NAME);
         req.setParameter(VM_ID_PARAM, reference.getIdString());
-        req.addListener(new HeapDumpListener(heapDumpCompleteAction));
+        req.addListener(new HeapDumpListener(heapDumpSuccessAction, heapDumpFailureAction));
 
         RequestQueue queue = OSGIUtils.getInstance().getService(RequestQueue.class);
         queue.putRequest(req);
--- a/client/heapdumper/core/src/main/resources/com/redhat/thermostat/client/heap/strings.properties	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/main/resources/com/redhat/thermostat/client/heap/strings.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -15,7 +15,7 @@
 HEAP_ID_NOT_FOUND = Heap ID not found: {0}
 HEAP_ID_REQUIRED = Heap ID required
 SEARCH_TERM_REQUIRED = A search term is required
-
+HEAP_DUMP_ERROR = Error dumping heap (agent: {0}, vm: {1})
 
 COMMAND_HEAP_DUMP_DONE = Done
 
--- a/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -68,6 +68,7 @@
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.common.dao.AgentInfoDAO;
 import com.redhat.thermostat.common.dao.HeapDAO;
+import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.heap.HeapDump;
@@ -162,7 +163,14 @@
         ApplicationCache cache = mock(ApplicationCache.class);
         when(appService.getApplicationCache()).thenReturn(cache);
         setUpTimers();
+
+        HostRef hostRef = mock(HostRef.class);
+        when(hostRef.getAgentId()).thenReturn("agent-id");
+
         VmRef ref = mock(VmRef.class);
+        when(ref.getIdString()).thenReturn("vm-id");
+        when(ref.getAgent()).thenReturn(hostRef);
+
         heapDumperCommand = mock(HeapDumperCommand.class);
         controller = new HeapDumpController(agentDao, vmDao, heapDao, ref, appService,
                 heapDumperCommand, viewProvider, detailsViewProvider,
@@ -285,12 +293,23 @@
         heapDumperListener.actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED));
 
         ArgumentCaptor<Runnable> heapDumpCompleteAction = ArgumentCaptor.forClass(Runnable.class);
-        verify(heapDumperCommand).execute(same(agentDao), any(VmRef.class), heapDumpCompleteAction.capture());
+        verify(heapDumperCommand).execute(same(agentDao), any(VmRef.class), heapDumpCompleteAction.capture(), any(Runnable.class));
         heapDumpCompleteAction.getValue().run();
         verify(view).notifyHeapDumpComplete();
-
     }
- 
+
+    @Test
+    public void testRequestHeapDumpFails() {
+        setUpListeners();
+
+        heapDumperListener.actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED));
+
+        ArgumentCaptor<Runnable> heapDumpFailedAction = ArgumentCaptor.forClass(Runnable.class);
+        verify(heapDumperCommand).execute(same(agentDao), any(VmRef.class), any(Runnable.class), heapDumpFailedAction.capture());
+        heapDumpFailedAction.getValue().run();
+        verify(view).displayWarning("Error dumping heap (agent: agent-id, vm: vm-id)");
+    }
+
     @SuppressWarnings("unchecked")
 	@Test
     public void testTimerChecksForNewHeapDumps() {
--- a/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -77,15 +77,15 @@
         when(osgi.getService(AgentInfoDAO.class)).thenReturn(agentInfoDao);
 
         HeapDumperCommand impl = mock(HeapDumperCommand.class);
-        final ArgumentCaptor<Runnable> arg = ArgumentCaptor.forClass(Runnable.class);
+        final ArgumentCaptor<Runnable> successHandler = ArgumentCaptor.forClass(Runnable.class);
         doAnswer(new Answer<Void>() {
 
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                arg.getValue().run();
+                successHandler.getValue().run();
                 return null;
             }
-        }).when(impl).execute(eq(agentInfoDao), any(VmRef.class), arg.capture());
+        }).when(impl).execute(eq(agentInfoDao), any(VmRef.class), successHandler.capture(), any(Runnable.class));
 
         DumpHeapCommand command = new DumpHeapCommand(osgi, impl);
 
@@ -97,7 +97,7 @@
 
         command.run(factory.createContext(args));
 
-        verify(impl).execute(eq(agentInfoDao), isA(VmRef.class), any(Runnable.class));
+        verify(impl).execute(eq(agentInfoDao), isA(VmRef.class), any(Runnable.class), any(Runnable.class));
         assertEquals("Done\n", factory.getOutput());
     }
 
--- a/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommandTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/core/src/test/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommandTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -38,6 +38,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -70,6 +71,7 @@
     private VmRef vmRef;
     private RequestQueue reqQueue;
     private Runnable heapDumpCompleteAction;
+    private Runnable heapDumpFailedAction;
 
     @Before
     public void setUp() {
@@ -91,7 +93,7 @@
         when(vmRef.getIdString()).thenReturn("123");
         when(vmRef.getAgent()).thenReturn(host);
         heapDumpCompleteAction = mock(Runnable.class);
-
+        heapDumpFailedAction = mock(Runnable.class);
     }
 
     @After
@@ -105,7 +107,7 @@
     @Test
 	public void testExecute() {
 
-        cmd.execute(agentInfoDao, vmRef, heapDumpCompleteAction);
+        cmd.execute(agentInfoDao, vmRef, heapDumpCompleteAction, heapDumpFailedAction);
 
 		ArgumentCaptor<Request> reqArg = ArgumentCaptor.forClass(Request.class);
 		verify(reqQueue).putRequest(reqArg.capture());
@@ -121,6 +123,29 @@
 		    l.fireComplete(req, new Response(ResponseType.OK));
 		}
 		verify(heapDumpCompleteAction).run();
+		verify(heapDumpFailedAction, times(0)).run();
+    }
+
+    @Test
+    public void testExecuteFailure() {
+
+        cmd.execute(agentInfoDao, vmRef, heapDumpCompleteAction, heapDumpFailedAction);
+
+        ArgumentCaptor<Request> reqArg = ArgumentCaptor.forClass(Request.class);
+        verify(reqQueue).putRequest(reqArg.capture());
+        Request req = reqArg.getValue();
+        assertEquals("com.redhat.thermostat.agent.heapdumper.internal.HeapDumpReceiver", req.getReceiver());
+        verifyClassExists(req.getReceiver());
+        assertEquals(RequestType.RESPONSE_EXPECTED, req.getType());
+        assertEquals("123", req.getParameter("vmId"));
+        assertEquals(new InetSocketAddress("test", 123), req.getTarget());
+
+        Collection<RequestResponseListener> ls = req.getListeners();
+        for (RequestResponseListener l : ls) {
+            l.fireComplete(req, new Response(ResponseType.ERROR));
+        }
+        verify(heapDumpCompleteAction, times(0)).run();
+        verify(heapDumpFailedAction).run();
     }
 
     private void verifyClassExists(String receiver) {
--- a/client/heapdumper/swing/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/heapdumper/swing/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java	Mon Dec 10 15:05:37 2012 +0100
@@ -43,6 +43,7 @@
 import java.util.List;
 
 import javax.swing.BoxLayout;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
 import javax.swing.event.ListSelectionEvent;
@@ -227,4 +228,9 @@
             }
         });
     }
+
+    @Override
+    public void displayWarning(String string) {
+        JOptionPane.showMessageDialog(visiblePane, string, "Warning", JOptionPane.WARNING_MESSAGE);
+    }
 }
--- a/client/memory-stats-panel/core/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <artifactId>thermostat-osgi-memory-stats-panel</artifactId>
-    <groupId>com.redhat.thermostat</groupId>
-    <version>0.5.0-SNAPSHOT</version>
-  </parent>
-  <artifactId>thermostat-osgi-memory-stats-panel-core</artifactId>
-  <packaging>bundle</packaging>
-  <name>Thermostat Client VM Memory Monitor Core plugin</name>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Export-Package>
-                com.redhat.thermostat.client.stats.memory.core,
-                   com.redhat.thermostat.client.stats.memory.core.locale,
-            </Export-Package>
-            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-SymbolicName>com.redhat.thermostat.client.stats.memory.core</Bundle-SymbolicName>
-            <!-- Do not autogenerate uses clauses in Manifests -->
-            <_nouses>true</_nouses>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.compendium</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.jfree</groupId>
-      <artifactId>jfreechart</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-client-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>    
-  </dependencies>
-</project>
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryMeter.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,433 +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.client.stats.memory.core;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Insets;
-import java.awt.Paint;
-import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RoundRectangle2D;
-import java.awt.image.BufferedImage;
-import java.beans.Transient;
-
-import javax.swing.JComponent;
-import javax.swing.plaf.ColorUIResource;
-
-import sun.swing.SwingUtilities2;
-
-@SuppressWarnings({ "restriction", "serial" })
-public class MemoryMeter extends JComponent {
-    
-    // TODO the font should be customizable
-    private static final Font font = new Font("SansSerif", Font.PLAIN, 10);
-    
-    private static final int TICK_NUM = 100;
-    private static final int SMALL_TICK_NUM = 5;
-    
-    private static final int MAIN_BAR_HEIGHT = 20;
-    
-    private static final ColorUIResource MAIN_GRADIENT_TOP = new ColorUIResource(0xf1f3f1);
-    private static final ColorUIResource MAIN_BORDER_COLOR = new ColorUIResource(0xa8aca8);
-    
-    private static final ColorUIResource MAIN_BAR_BASE_COLOR_TOP = new ColorUIResource(0xbcd5ef);
-    private static final ColorUIResource MAIN_BAR_BASE_COLOR = new ColorUIResource(0x4A90D9);
-    
-    //private static final ColorUIResource STATS_BG = new ColorUIResource(0xF8F8F8);
-    private static final ColorUIResource STATS_BG = new ColorUIResource(0xFFFFFF);
-    
-    private ColorUIResource tickColor;
-    
-    private RangeModel primary;
-    private RangeModel secondary;
-    
-    private Insets boundInsets;
-    
-    private RangeModel internalSecondaryModel;
-    
-    private StatsModel primaryStats;
-    
-    private String primaryUnit;
-    private String secondaryUnit;
-
-        public void setPrimaryScaleUnit(String primaryUnit) {
-        this.primaryUnit = primaryUnit;
-    }
-    
-    public void setSecondayScaleUnit(String secondaryUnit) {
-        this.secondaryUnit = secondaryUnit;
-    }
-    
-    public StatsModel getStats() {
-        return primaryStats;
-    }
-    
-    public void setStats(StatsModel primaryStats) {
-        this.primaryStats = primaryStats;
-    }
-
-    public MemoryMeter() {
-        
-        secondaryUnit = "";
-        primaryUnit = "";
-        
-        boundInsets = new Insets(10, 10, 20, 20);
-        
-        tickColor = new ColorUIResource(0xdbdddb);
-        
-        primary = new RangeModel();
-        primary.setMinimum(0);
-        primary.setMaximum(100);
-        primary.setMinNormalized(0);
-        primary.setMaxNormalized(100);
-        
-        secondary = new RangeModel();
-        secondary.setMinimum(0);
-        secondary.setMaximum(100);
-        secondary.setMinNormalized(0);
-        secondary.setMaxNormalized(100);
-        
-        internalSecondaryModel = new RangeModel();
-    }
-    
-    public ColorUIResource getTickColor() {
-        return tickColor;
-    }
-    
-    public void setTickColor(ColorUIResource tickColor) {
-        this.tickColor = tickColor;
-    }
-    
-    public RangeModel getPrimaryModel() {
-        return primary;
-    }
-    
-    public RangeModel getSecondaryModel() {
-        return secondary;
-    }
-    
-    protected Rectangle getOuterBounds() {
-        return new Rectangle(0, 0, getWidth(), getHeight());
-    }
-    
-    protected Rectangle getBoundsWithInsets(Rectangle bounds) {
-        return new Rectangle(bounds.x + boundInsets.left,
-                             bounds.y + boundInsets.top,
-                             bounds.width  - boundInsets.right,
-                             bounds.height - boundInsets.bottom);
-    }
-        
-    /**
-     * paint the outher frame, including the light border sorrounding
-     */
-    protected void paintOuterFrame(Graphics2D graphics, Rectangle bounds) {
-
-        RoundRectangle2D frame = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width, bounds.height, 6, 6);
-
-        Paint paint = new GradientPaint(0, 0, MAIN_GRADIENT_TOP, 0, getHeight(), getBackground());
-        graphics.setPaint(paint);
-        graphics.fill(frame);
-        
-        paint = new GradientPaint(0, 0, MAIN_BORDER_COLOR, 0, getHeight(), getBackground());
-        graphics.setPaint(paint);
-        frame = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width -1, bounds.height, 6, 6);
-        graphics.draw(frame);
-    }
-    
-    /**
-     * paint the track sorrounding the main bar
-     */
-    protected void paintMainBarTrackFill(Graphics2D graphics, Rectangle bounds) {
-        
-        Paint paint = new GradientPaint(0, 0, MAIN_GRADIENT_TOP, 0, bounds.height, Color.WHITE);
-        graphics.setPaint(paint);
-        RoundRectangle2D frame = new RoundRectangle2D.Float(0, 0, bounds.width, bounds.height, 6, 6);
-        graphics.fill(frame);
-    }
-    
-    /**
-     */
-    protected void paintMainBarTrackBorder(Graphics2D graphics, Rectangle bounds) {
-        Paint paint = new GradientPaint(0, 0, MAIN_BORDER_COLOR, getWidth(), 0, getBackground());
-        graphics.setPaint(paint);
-        
-        RoundRectangle2D frame = new RoundRectangle2D.Float(0, 0, bounds.width - 1, bounds.height, 6, 6);
-        graphics.draw(frame);
-    }
-
-    
-    /**
-     * this is the main bar, will it up to what is defined by the model
-     */
-    protected void paintMainBarFill(Graphics2D graphics, Rectangle bounds) {
-        Paint paint = new GradientPaint(0, 0, MAIN_BAR_BASE_COLOR, getWidth() * 2, 0, getBackground());
-        graphics.setPaint(paint);
-        
-        RoundRectangle2D frame =
-                new RoundRectangle2D.Float(0, 0, getPrimaryModel().getValueNormalized(),
-                                           bounds.height, 6, 6);
-        graphics.fill(frame);
-        
-        String value = String.valueOf(getPrimaryModel().getValue()) + " " + primaryUnit;
-        Rectangle2D fontBounds = font.getStringBounds(value, graphics.getFontRenderContext());
-        int width = (int) (bounds.width/2 - fontBounds.getWidth()/2) - 1;
-        
-        if (width > getPrimaryModel().getValueNormalized()) {
-            graphics.setPaint(MAIN_BAR_BASE_COLOR);
-        } else {
-            graphics.setPaint(getBackground());
-        }
-
-        int height  = (int) (bounds.height / 2 + fontBounds.getHeight()/2);
-        SwingUtilities2.drawString(this, graphics, value, width, height);
-    }
-    
-    /**
-     */
-    private void paintMainBar(Graphics2D g, Rectangle bounds) {
-        
-        Graphics2D graphics = (Graphics2D) g.create();
-                
-        graphics.translate(bounds.x, bounds.y);
-        paintMainBarTrackFill(graphics, bounds);
-        
-        paintMainBarFill(graphics, bounds);
-        
-        paintMainBarTrackBorder(graphics, bounds);
-        graphics.dispose();
-    }
-    
-    /**
-     */
-    protected void drawBottomBar(Graphics2D g, Rectangle bounds) {
-
-        Graphics2D graphics = (Graphics2D) g.create();
-        graphics.translate(bounds.x, bounds.y + bounds.height);
-        
-        drawTickMark(graphics, bounds);
-        
-        paintSecondaryBarFill(graphics, bounds);
-        
-        graphics.dispose();
-    }
-    
-    /**
-     */
-    protected void drawTickMark(Graphics2D graphics, Rectangle bounds) {
-      
-        graphics.setPaint(MAIN_BORDER_COLOR);
-        
-        int smallTop = bounds.height - 5;
-        int smallBottom = bounds.height;
-
-        int mainTop = smallTop + 20;
-        int mainBottom = smallBottom - 15;
-      
-        // the first and last tick are always big, this is the first
-        graphics.drawLine(0, mainTop, 0, mainBottom);
-      
-        // the space between the vertical lines
-        double tickSpace = ((double) bounds.width) / TICK_NUM;
-      
-        internalSecondaryModel.setMaxNormalized(bounds.width);      
-        int numTicks = 0;
-
-        for (int x = 0; x < bounds.width; x += tickSpace + 0.5) {
-            if (numTicks % SMALL_TICK_NUM == 0) {
-              
-                graphics.drawLine(x, smallTop, x, smallBottom + 5);
-
-                internalSecondaryModel.setValue(x);
-            } else {
-                graphics.drawLine(x, smallTop, x, smallBottom);
-            }
-            numTicks++;
-        }
-        
-        // that's the last
-        graphics.drawLine(bounds.width, mainTop, bounds.width, mainBottom);
-        graphics.drawLine(0, smallTop, bounds.width, smallTop);
-        
-        drawStrings(graphics, mainBottom, mainTop, bounds.width);
-    }
-    
-    protected void drawStrings(Graphics2D graphics, int top, int bottom, int right) {
-      
-      // now draw the min/max values of both side of markers      
-      // top bar min value
-      FontMetrics fm = SwingUtilities2.getFontMetrics(this, font);
-      
-      String value = String.valueOf(getPrimaryModel().getMinimum()) + " " + primaryUnit;
-      int height = top + fm.getAscent()/2;
-      SwingUtilities2.drawString(this, graphics, value, 1, height);
-      
-      value = String.valueOf(getSecondaryModel().getMinimum()) + " " + secondaryUnit;
-      height = bottom;
-      SwingUtilities2.drawString(this, graphics, value, 1, height);
-      
-      value = String.valueOf(getPrimaryModel().getMaximum()) + " " + primaryUnit;
-      height = top + fm.getAscent()/2;
-
-      int width = (int) (right - font.getStringBounds(value, graphics.getFontRenderContext()).getWidth()) - 1;
-      SwingUtilities2.drawString(this, graphics, value, width, height);
-      
-      value = String.valueOf(getSecondaryModel().getMaximum()) + " " + secondaryUnit;
-      height = bottom;
-      width = (int) (right - font.getStringBounds(value, graphics.getFontRenderContext()).getWidth()) - 1;
-      SwingUtilities2.drawString(this, graphics, value, width, height);
-      
-      // now draw the actual value for the bottom bar, the top bar is drawn in
-      // its fill method
-      value = String.valueOf(getSecondaryModel().getValue()) + " " + secondaryUnit;
-      width = right/2;
-      Rectangle2D bounds = font.getStringBounds(value, graphics.getFontRenderContext());
-      width = (int) (width - bounds.getWidth()/2) - 1;
-      SwingUtilities2.drawString(this, graphics, value, width, height);
-      RoundRectangle2D frame = new RoundRectangle2D.Double(width - 2, height - bounds.getHeight(),
-                                                          bounds.getWidth() + 4, bounds.getHeight() + 4,
-                                                          4, 4);
-      graphics.draw(frame);
-    }
-    
-    protected void paintSecondaryBarFill(Graphics2D graphics, Rectangle bounds) {
-        
-        graphics.setPaint(MAIN_BAR_BASE_COLOR_TOP);
-        graphics.drawLine(1, bounds.height, getSecondaryModel().getValueNormalized() - 1, bounds.height);
-        
-        graphics.setPaint(MAIN_BAR_BASE_COLOR);
-        graphics.fillRect(1, bounds.height + 1, getSecondaryModel().getValueNormalized(), 2);
-    }
-    
-    protected void paintStats(Graphics2D graphics, Rectangle bounds) {
-        
-        int imageWidth = bounds.width - 2;
-        if (imageWidth < 0 || bounds.height < 0) {
-            return;
-        }
-        
-        StatsModel stats = getStats();
-        drawStats(graphics, stats, bounds.x, bounds.y, imageWidth, bounds.height);
-    }
-    
-    private void drawStats(Graphics2D graphics, StatsModel stats, int x, int y, int imageWidth, int height) {
-        if (stats != null) {
-            BufferedImage image = stats.getChart(imageWidth, height, STATS_BG,
-                                                 new ColorUIResource(getForeground()));
-       
-            paintStatsLabel(graphics, image, stats.getName());
-            graphics.drawImage(image, x, y, null);
-        }
-    }
-    
-    protected void paintStatsLabel(Graphics2D graphics, BufferedImage image, String label) {
-        int height = SwingUtilities2.getFontMetrics(this, font).getAscent() + 2;
-        if (height <= 0) {
-            return;
-        }
-        
-        Graphics2D imageGraphics = (Graphics2D) image.getGraphics();
-        imageGraphics.setColor(getForeground());
-        imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        
-        SwingUtilities2.drawString(this, imageGraphics, label, 2, height);
-    }
-    
-    @Override
-    protected void paintComponent(Graphics g) {
-
-        Graphics2D graphics = (Graphics2D) g.create();
-        graphics.setFont(font);
-        
-        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-        
-        Rectangle outerBounds = getOuterBounds();
-        Rectangle innerBounds = getBoundsWithInsets(outerBounds);
-        Rectangle statsBound =  getBoundsWithInsets(outerBounds);
-        
-        // move the bar close to the center
-        innerBounds.height = MAIN_BAR_HEIGHT;
-        innerBounds.y = outerBounds.height/2;
-        
-        // make the stats area cover the upper portion instead
-        statsBound.height = (outerBounds.height/2) - MAIN_BAR_HEIGHT;
-        
-        resetModels(0, innerBounds.width);
-
-        // some eye candy
-        paintOuterFrame(graphics, outerBounds);
-
-        // paint the usage stats
-        paintStats(graphics, statsBound);
-        
-        // main and bottom bars
-        paintMainBar(graphics, innerBounds);
-        drawBottomBar(graphics, innerBounds);
-        
-        graphics.dispose();
-    }
-    
-    private void resetModels(int min, int max) {
-        
-        getPrimaryModel().setMaxNormalized(max);
-        getPrimaryModel().setMinNormalized(min);
-        
-        RangeModel model = getSecondaryModel();
-        model.setMaxNormalized(max);
-        model.setMinNormalized(min);
-        
-        internalSecondaryModel.setMaximum(model.getMaximum());
-        internalSecondaryModel.setMinimum(model.getMinimum());
-        internalSecondaryModel.setValue(model.getValue());
-        
-        internalSecondaryModel.setMaxNormalized(max);
-        internalSecondaryModel.setMinNormalized(0);
-    }
-    
-    @Override
-    @Transient
-    public Dimension getPreferredSize() {
-        return new Dimension(850, 150);
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsController.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +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.client.stats.memory.core;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import com.redhat.thermostat.client.core.controllers.VmInformationServiceController;
-import com.redhat.thermostat.client.core.views.BasicView.Action;
-import com.redhat.thermostat.client.core.views.UIComponent;
-import com.redhat.thermostat.client.stats.memory.core.locale.LocaleResources;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.NotImplementedException;
-import com.redhat.thermostat.common.Timer;
-import com.redhat.thermostat.common.Timer.SchedulingType;
-import com.redhat.thermostat.common.dao.AgentInfoDAO;
-import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
-import com.redhat.thermostat.gc.remote.common.GCRequest;
-import com.redhat.thermostat.gc.remote.common.command.GCCommand;
-import com.redhat.thermostat.storage.model.VmMemoryStat;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
-
-class MemoryStatsController implements VmInformationServiceController {
-
-    private final MemoryStatsView view;
-    private final VmMemoryStatDAO vmDao;
-   
-    private final VmRef ref;
-    private final Timer timer;
-    
-    private final Map<String, Payload> regions;
-    
-    private VMCollector collector;
-    
-    class VMCollector implements Runnable {
-
-        private long desiredUpdateTimeStamp = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
-
-        @Override
-        public void run() {
-            List<VmMemoryStat> vmInfo = vmDao.getLatestVmMemoryStats(ref, desiredUpdateTimeStamp);
-            for (VmMemoryStat memoryStats: vmInfo) {
-                Generation[] generations = memoryStats.getGenerations();
-                
-                for (Generation generation : generations) {
-                    Space[] spaces = generation.getSpaces();
-                    for (Space space: spaces) {
-                        Payload payload = regions.get(space.getName());
-                        if (payload == null) {
-                            payload = new Payload();
-                            payload.setName(space.getName());
-                        }
-
-                        Scale usedScale = normalizeScale(space.getUsed(), space.getCapacity());
-                        double used = Scale.convertTo(usedScale, space.getUsed(), 100);
-                        double maxUsed = Scale.convertTo(usedScale, space.getCapacity(), 100);
-                        
-                        payload.setUsed(used);
-                        payload.setMaxUsed(maxUsed);
-                        payload.setUsedUnit(usedScale);
-                        
-                        Scale maxScale = normalizeScale(space.getCapacity(), space.getMaxCapacity());
-                        double capacity = Scale.convertTo(maxScale, space.getCapacity(), 100);
-                        double maxCapacity = Scale.convertTo(maxScale, space.getMaxCapacity(), 100);
-                        
-                        payload.setCapacity(capacity);
-                        payload.setMaxCapacity(maxCapacity);
-                        payload.setCapacityUnit(maxScale);
-                        
-                        String tooltip = space.getName() + ": used: " + used + " " + usedScale +
-                                ", capacity: " + capacity + " " + maxScale +
-                                ", max capacity: " + maxCapacity + " " + maxScale;
-                        
-                        payload.setTooltip(tooltip);
-                        
-                        StatsModel model = payload.getModel();
-                        if (model == null) {
-                            model = new StatsModel();
-                            model.setName(space.getName());
-                            model.setRange(3600);
-                        }
-                        
-                        // normalize this always in the same unit
-                        model.addData(memoryStats.getTimeStamp(),
-                                      Scale.convertTo(Scale.MiB, space.getUsed(), 100));
-                        
-                        payload.setModel(model);
-                        if (regions.containsKey(space.getName())) {
-                            view.updateRegion(payload.clone());
-                        } else {
-                            view.addRegion(payload.clone());
-                            regions.put(space.getName(), payload);
-                        }
-                        
-                        view.requestRepaint();
-                        desiredUpdateTimeStamp = Math.max(desiredUpdateTimeStamp, memoryStats.getTimeStamp());
-                    }
-                }
-            }
-        }
-    }
-    
-    public MemoryStatsController(ApplicationService appSvc, final VmMemoryStatDAO vmMemoryStatDao,
-                                 final VmRef ref, MemoryStatsViewProvider viewProvider,
-                                 final AgentInfoDAO agentDAO, final GCRequest gcRequest) {
-        
-        regions = new HashMap<>();
-        this.ref = ref;
-        vmDao = vmMemoryStatDao;
-        view = viewProvider.createView();
-        
-        timer = appSvc.getTimerFactory().createTimer();
-        
-        collector = new VMCollector();
-        timer.setAction(collector);
-        
-        timer.setInitialDelay(0);
-        timer.setDelay(1000);
-        timer.setTimeUnit(TimeUnit.MILLISECONDS);
-        timer.setSchedulingType(SchedulingType.FIXED_RATE);
-
-        view.addActionListener(new ActionListener<Action>() {
-            @Override
-            public void actionPerformed(ActionEvent<Action> actionEvent) {
-                switch(actionEvent.getActionId()) {
-                    case HIDDEN:
-                        stop();
-                        break;
-                        
-                    case VISIBLE:
-                        start();
-                        break;
-                        
-                    default:
-                        throw new NotImplementedException("unknown event: " + actionEvent.getActionId());
-                }
-            }
-        });
-        
-        view.addGCActionListener(new ActionListener<GCCommand>() {
-            @Override
-            public void actionPerformed(ActionEvent<GCCommand> actionEvent) {
-                gcRequest.sendGCRequestToAgent(ref, agentDAO);
-            }
-        });
-    }
-    
-    // for testing
-    VMCollector getCollector() {
-        return collector;
-    };
-    
-    Map<String, Payload> getRegions() {
-        return regions;
-    }
-    
-    private Scale normalizeScale(long min, long max) {
-        // FIXME: this is very dumb and very inefficient
-        // needs cleanup
-        Scale minScale = Scale.getScale(min);
-        Scale maxScale = Scale.getScale(max);
-        
-        Scale[] scales = Scale.values();
-        int maxID = 0;
-        int minID = 0;
-        for (int i = 0; i < scales.length; i++) {
-            if (scales[i] == minScale) {
-                minID = i;
-            }
-            if (scales[i] == maxScale) {
-                maxID = i;
-            }
-        }
-        while (maxID - minID >= 2) {
-            minID++;
-        }
-        return scales[minID];
-    }
-    
-    private void start() {
-        timer.start();
-    }
-
-    private void stop() {
-        timer.stop();
-    }
-
-    @Override
-    public String getLocalizedName() {
-        Translate<LocaleResources> t = LocaleResources.createLocalizer();
-        return t.localize(LocaleResources.VM_INFO_TAB_MEMORY);
-    }
-
-    @Override
-    public UIComponent getView() {
-        return (UIComponent) view;
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsService.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +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.client.stats.memory.core;
-
-import com.redhat.thermostat.client.core.VmFilter;
-import com.redhat.thermostat.client.core.VmInformationService;
-import com.redhat.thermostat.client.core.controllers.VmInformationServiceController;
-import com.redhat.thermostat.client.osgi.service.AlwaysMatchFilter;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.dao.AgentInfoDAO;
-import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.common.utils.OSGIUtils;
-import com.redhat.thermostat.gc.remote.common.GCRequest;
-
-public class MemoryStatsService implements VmInformationService {
-    
-    private static final int PRIORITY = PRIORITY_MEMORY_GROUP + 40;
-    private VmFilter filter = new AlwaysMatchFilter();
-
-    private ApplicationService appSvc;
-    private VmMemoryStatDAO vmMemoryStatDao;
-    private AgentInfoDAO agentDAO;
-    private GCRequest gcRequest;
-    
-    public MemoryStatsService(ApplicationService appSvc, VmMemoryStatDAO vmMemoryStatDao, AgentInfoDAO agentDAO, GCRequest gcRequest) {
-        this.appSvc = appSvc;
-        this.vmMemoryStatDao = vmMemoryStatDao;
-        this.gcRequest = gcRequest;
-        this.agentDAO = agentDAO;
-    }
-    
-    @Override
-    public VmInformationServiceController getInformationServiceController(VmRef ref) {
-        MemoryStatsViewProvider viewProvider = OSGIUtils.getInstance().getService(MemoryStatsViewProvider.class);
-        return new MemoryStatsController(appSvc, vmMemoryStatDao, ref, viewProvider, agentDAO, gcRequest);
-    }
-
-    @Override
-    public VmFilter getFilter() {
-        return filter;
-    }
-
-    @Override
-    public int getPriority() {
-        return PRIORITY;
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsView.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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.client.stats.memory.core;
-
-import com.redhat.thermostat.client.core.views.BasicView;
-import com.redhat.thermostat.client.core.views.UIComponent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.gc.remote.common.command.GCCommand;
-
-public abstract class MemoryStatsView extends BasicView implements UIComponent {
-    
-    public abstract void addRegion(Payload region);
-    public abstract void updateRegion(Payload region);
-    
-    public abstract void addGCActionListener(ActionListener<GCCommand> listener);
-    
-    public abstract void requestRepaint();    
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsViewProvider.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +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.client.stats.memory.core;
-
-import com.redhat.thermostat.client.core.views.ViewProvider;
-
-public interface MemoryStatsViewProvider extends ViewProvider {
-
-    @Override
-    public MemoryStatsView createView();
-    
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/Payload.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +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.client.stats.memory.core;
-
-import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
-
-public class Payload implements Cloneable {
-
-    private String name;
-    private String tooltip;
-    
-    private double capacity;
-    private double maxCapacity;
-    private double maxUsed;
-    private double used;
-    
-    private Scale usedUnit;
-    private Scale capacityUnit;
-    
-    private StatsModel model;
-    
-    public void setModel(StatsModel model) {
-        this.model = model;
-    }
-    
-    public StatsModel getModel() {
-        return model;
-    }
-    
-    public void setCapacityUnit(Scale capacityUnit) {
-        this.capacityUnit = capacityUnit;
-    }
-    
-    public Scale getCapacityUnit() {
-        return capacityUnit;
-    }
-    
-    public void setUsedUnit(Scale usedUnit) {
-        this.usedUnit = usedUnit;
-    }
-    
-    public Scale getUsedUnit() {
-        return usedUnit;
-    }
-    
-    public double getMaxCapacity() {
-        return maxCapacity;
-    }
-    
-    public void setMaxCapacity(double maxCapacity) {
-        this.maxCapacity = maxCapacity;
-    }
-    
-    public double getUsed() {
-        return used;
-    }
-    
-    public void setUsed(double used) {
-        this.used = used;
-    }
-    
-    public double getMaxUsed() {
-        return maxUsed;
-    }
-    
-    public void setMaxUsed(double maxUsed) {
-        this.maxUsed = maxUsed;
-    }
-    
-    public double getCapacity() {
-        return capacity;
-    }
-    
-    public void setCapacity(double capacity) {
-        this.capacity = capacity;
-    }
-    
-    public String getName() {
-        return name;
-    }
-    
-    public void setName(String name) {
-        this.name = name;
-    }
-    
-    public String getTooltip() {
-        return tooltip;
-    }
-    
-    public void setTooltip(String tooltip) {
-        this.tooltip = tooltip;
-    }
-    
-    @Override
-    protected Payload clone() {
-        
-        Payload copy = new Payload();
-        
-        copy.used = used;
-        copy.capacity = capacity;
-        copy.capacityUnit = capacityUnit;
-        copy.maxCapacity = maxCapacity;
-        copy.maxUsed = maxUsed;
-        copy.model = model.clone();
-        copy.name = name;
-        copy.tooltip = tooltip;
-        copy.usedUnit = usedUnit;
-        
-        return copy;
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/RangeModel.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +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.client.stats.memory.core;
-
-public class RangeModel {
-
-    private int minNormalized;
-    private double min;
-    
-    private int maxNormalized;
-    private double max;
- 
-    private double value;
- 
-    public double getMinimum() {
-        return min;
-    }
-
-    public void setMinimum(double newMinimum) {
-        this.min = newMinimum;
-    }
-
-    public double getMaximum() {
-        return max;
-    }
-
-    public void setMaximum(double newMaximum) {
-        this.max = newMaximum;
-    }
-
-    public void setMaxNormalized(int maxNormalized) {
-        this.maxNormalized = maxNormalized;
-    }
-    
-    public void setMinNormalized(int minNormalized) {
-        this.minNormalized = minNormalized;
-    }
-    
-    public double getValue() {
-        return value;
-    }
-
-    public void setValue(double newValue) {
-        this.value = newValue;
-    }
-    
-    int getMaxNormalized() {
-        return maxNormalized;
-    }
-    
-    int getMinNormalized() {
-        return minNormalized;
-    }
-    
-    public int getValueNormalized() {
-        double normalized = ((value - min) * (maxNormalized - minNormalized)/(max - min)) + minNormalized;
-        return (int) Math.round(normalized);
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/StatsModel.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +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.client.stats.memory.core;
-
-import java.awt.Graphics2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.util.Date;
-
-import javax.swing.plaf.ColorUIResource;
-
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.axis.DateAxis;
-import org.jfree.chart.axis.NumberAxis;
-import org.jfree.chart.plot.XYPlot;
-import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
-import org.jfree.data.time.Millisecond;
-import org.jfree.data.time.TimeSeries;
-import org.jfree.data.time.TimeSeriesCollection;
-import org.jfree.data.xy.XYDataset;
-import org.jfree.ui.RectangleInsets;
-
-public class StatsModel implements Cloneable {
-
-    private static final String lock = new String("MemoryStatsModelLock");
-
-    private String name;
-    
-    private TimeSeries dataSet;
-    
-    public StatsModel() {
-        dataSet = new TimeSeries("");
-    }
-    
-    BufferedImage getChart(int width, int height, ColorUIResource bgColor, ColorUIResource fgColor) {
-        JFreeChart chart = createChart(bgColor, fgColor);
-        
-        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-        chart.draw((Graphics2D) image.getGraphics(), new Rectangle2D.Double(0, 0, width, height), null);
-        
-        return image;
-    }
-    
-    public String getName() {
-        return name;
-    }
-    
-    public void setName(String name) {
-        dataSet.setDescription(name);
-        this.name = name;
-    }
-    
-    public void setRange(int seconds) {
-        dataSet.setMaximumItemCount(seconds);
-    }
-    
-    public void addData(long timestamp, double value) {
-        Millisecond millisecond = new Millisecond(new Date(timestamp));
-        synchronized (lock) {
-            if (dataSet.getValue(millisecond) == null) {
-                dataSet.add(millisecond, value);
-                dataSet.removeAgedItems(true);
-            }
-        }
-    }
-    
-    @Override
-    protected StatsModel clone() {
-        
-        StatsModel model = new StatsModel();
-        model.setName(name);
-        model.setRange(dataSet.getMaximumItemCount());
-        
-        try {
-            model.dataSet = dataSet.createCopy(0, dataSet.getItemCount() - 1);
-        } catch (CloneNotSupportedException e) {
-            // ah... it's supported here...
-            e.printStackTrace();
-        }
-        
-        return model;
-    }
-    
-    /**
-     * Creates a chart.
-     *
-     * @return a chart.
-     */
-    private JFreeChart createChart(ColorUIResource bgColor, ColorUIResource fgColor) {
-
-        XYDataset priceData = null;
-        synchronized (lock) {            
-            try {
-                priceData = new TimeSeriesCollection(dataSet.createCopy(0,
-                                                     dataSet.getItemCount() - 1));
-            } catch (CloneNotSupportedException e) {
-                e.printStackTrace();
-            }
-        }
-
-        XYPlot plot = new XYPlot();
-        
-        plot.setDomainGridlinesVisible(false);
-        plot.setDomainCrosshairVisible(false);
-        plot.setRangeGridlinesVisible(false);
-        plot.setRangeCrosshairVisible(false);
-                
-        DateAxis dateAxis = new DateAxis();
-        
-        dateAxis.setTickLabelsVisible(false);
-        dateAxis.setTickMarksVisible(false);
-        dateAxis.setAxisLineVisible(false);
-        dateAxis.setNegativeArrowVisible(false);
-        dateAxis.setPositiveArrowVisible(false);
-        dateAxis.setVisible(false);
-        
-        NumberAxis numberAxis = new NumberAxis();
-        numberAxis.setTickLabelsVisible(false);
-        numberAxis.setTickMarksVisible(false);
-        numberAxis.setAxisLineVisible(false);
-        numberAxis.setNegativeArrowVisible(false);
-        numberAxis.setPositiveArrowVisible(false);
-        numberAxis.setVisible(false);
-        numberAxis.setAutoRangeIncludesZero(false);
-        
-        plot.setDomainAxis(dateAxis);
-        plot.setRangeAxis(numberAxis);
-        plot.setDataset(priceData);
-        
-        plot.setInsets(new RectangleInsets(-1, -1, 0, 0));
-        
-        plot.setRenderer(new StandardXYItemRenderer(StandardXYItemRenderer.LINES));
-        plot.setBackgroundPaint(bgColor);
-        
-        JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false);
-        
-        plot.getRenderer().setSeriesPaint(0, fgColor);
-        chart.setAntiAlias(true);
-        chart.setBorderVisible(false);
-        
-        return chart;
-    }
-    
-    TimeSeries getDataSet() {
-        return dataSet;
-    }
-}
--- a/client/memory-stats-panel/core/src/main/java/com/redhat/thermostat/client/stats/memory/core/locale/LocaleResources.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +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.client.stats.memory.core.locale;
-
-import com.redhat.thermostat.common.locale.Translate;
-
-public enum LocaleResources {
-
-    VM_INFO_TAB_MEMORY,
-    RESOURCE_MISSING;
-    
-    public static final String RESOURCE_BUNDLE =
-            "com.redhat.thermostat.client.stats.memory.locale.strings";
-    
-    public static Translate<LocaleResources> createLocalizer() {
-        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
-    }
-}
--- a/client/memory-stats-panel/core/src/main/resources/com/redhat/thermostat/client/stats/memory/locale/strings.properties	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-RESOURCE_MISSING = Missing translation!
-VM_INFO_TAB_MEMORY = Memory
\ No newline at end of file
--- a/client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/MemoryStatsControllerTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,285 +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.client.stats.memory.core;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.Timer;
-import com.redhat.thermostat.common.Timer.SchedulingType;
-import com.redhat.thermostat.common.TimerFactory;
-import com.redhat.thermostat.common.dao.AgentInfoDAO;
-import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.gc.remote.common.GCRequest;
-import com.redhat.thermostat.gc.remote.common.command.GCCommand;
-import com.redhat.thermostat.storage.model.VmMemoryStat;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
-import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
-
-public class MemoryStatsControllerTest {
-
-    private Generation[] generations = new Generation[2];
-    
-    private VmMemoryStatDAO memoryStatDao;
-    private MemoryStatsView view;
-    private Timer timer;
-    
-    private ActionListener<MemoryStatsView.Action> viewListener;
-    private ActionListener<GCCommand> gcActionListener;
-
-    private MemoryStatsController controller;
-    
-    private Space canary;
-
-    private AgentInfoDAO agentDAO;
-    private GCRequest gcRequest;
-    
-    private VmRef ref;
-    
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Before
-    public void setUp() {
-        timer = mock(Timer.class);
-        ArgumentCaptor<Runnable> actionCaptor = ArgumentCaptor.forClass(Runnable.class);
-        doNothing().when(timer).setAction(actionCaptor.capture());
-        
-        TimerFactory timerFactory = mock(TimerFactory.class);
-        when(timerFactory.createTimer()).thenReturn(timer);
-        ApplicationService appSvc = mock(ApplicationService.class);
-        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
-        
-        List<VmMemoryStat> vmInfo = new ArrayList<>();
-        
-        for (int i = 0; i < 2; i++) {
-            Generation generation = new Generation();
-            generation.setName("fluff" + i);
-            VmMemoryStat.Space[] spaces = new VmMemoryStat.Space[2 + (1 - i)]; 
-            for (int j = 0; j < 2; j++) {
-                Space space = new Space();
-                space.setName("fluffer" + i + j);
-                space.setUsed(100);
-                space.setCapacity(1000);
-                space.setMaxCapacity(10000);
-                spaces[j] = space;
-            }
-            if (i == 0) {
-                // special payload because the others have all the same values
-                canary = new Space();
-                canary.setName("canary");
-                canary.setUsed(1);
-                canary.setCapacity(2);
-                canary.setMaxCapacity(3);
-                spaces[2] = canary;
-            }
-            generation.setSpaces(spaces);
-            generations[i] = generation;
-        }
-        
-        long timestamp = 1;
-        int vmID = 1;
-        for (int i = 0; i < 5; i++) {
-            VmMemoryStat vmMemory = new VmMemoryStat(timestamp++, vmID, generations);
-            vmInfo.add(vmMemory);
-        }
-        
-        memoryStatDao = mock(VmMemoryStatDAO.class);
-        when(memoryStatDao.getLatestVmMemoryStats(any(VmRef.class), anyLong())).thenReturn(vmInfo);
-        
-        view = mock(MemoryStatsView.class);
-        MemoryStatsViewProvider viewProvider = mock(MemoryStatsViewProvider.class);
-        when(viewProvider.createView()).thenReturn(view);
-        
-        ArgumentCaptor<ActionListener> viewArgumentCaptor =
-                ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(view).addActionListener(viewArgumentCaptor.capture());
-
-        ArgumentCaptor<ActionListener> gcArgumentCaptor =
-                ArgumentCaptor.forClass(ActionListener.class);
-        doNothing().when(view).addGCActionListener(gcArgumentCaptor.capture());
-        
-        ref = mock(VmRef.class);
-        
-        agentDAO = mock(AgentInfoDAO.class);
-        gcRequest = mock(GCRequest.class);
-        
-        controller = new MemoryStatsController(appSvc, memoryStatDao, ref, viewProvider, agentDAO, gcRequest);
-        
-        viewListener = viewArgumentCaptor.getValue();
-        gcActionListener = gcArgumentCaptor.getValue();
-    }
-    
-    @Test
-    public void testStartStopTimer() {
-        viewListener.actionPerformed(new ActionEvent<>(view, MemoryStatsView.Action.VISIBLE));
-
-        verify(timer).start();
-        verify(timer).setSchedulingType(SchedulingType.FIXED_RATE);
-
-        viewListener.actionPerformed(new ActionEvent<>(view, MemoryStatsView.Action.HIDDEN));
-
-        verify(timer).stop();
-    }
-
-    @Test
-    public void testGCInvoked() {
-        gcActionListener.actionPerformed(new ActionEvent<>(view, GCCommand.REQUEST_GC));
-        verify(gcRequest).sendGCRequestToAgent(ref, agentDAO);
-    }
-    
-    @Test
-    public void testPayloadContainSpaces() {
-        MemoryStatsController.VMCollector collettor = controller.getCollector();
-        collettor.run();
-        
-        Map<String, Payload> regions = controller.getRegions();
-        assertEquals(5, regions.size());
-        
-        assertTrue(regions.containsKey("fluffer00"));
-        assertTrue(regions.containsKey("fluffer01"));
-        assertTrue(regions.containsKey("fluffer10"));
-        assertTrue(regions.containsKey("fluffer11"));
-        
-        assertTrue(regions.containsKey("canary"));
-    }
-    
-    @Test
-    public void testValues() {
-        MemoryStatsController.VMCollector collettor = controller.getCollector();
-        collettor.run();
-        
-        Map<String, Payload> regions = controller.getRegions();
-
-        Payload payload = regions.get("fluffer00");
-        assertEquals("fluffer00", payload.getName());
-        assertEquals(10000, payload.getMaxCapacity(), 0);
-        assertEquals(1000, payload.getCapacity(), 0);
-        assertEquals(100, payload.getUsed(), 0);
-        
-        payload = regions.get("canary");
-        assertEquals("canary", payload.getName());
-        assertEquals(3, payload.getMaxCapacity(), 0);
-        assertEquals(2, payload.getCapacity(), 0);
-        assertEquals(1, payload.getUsed(), 0);
-        
-        // the value above all ensure the same scale is used
-        String tooltip = payload.getName() + ": used: " + payload.getUsed() + " " +
-                         payload.getUsedUnit() + ", capacity: " +
-                         payload.getCapacity() + " " + payload.getUsedUnit() +
-                         ", max capacity: " + payload.getMaxCapacity() + " " +
-                         payload.getUsedUnit();
-        
-        assertEquals(tooltip, payload.getTooltip());
-    }
-    
-
-    @Test
-    public void testTimerFetchesMemoryDataDeltaOnly() {
-        ArgumentCaptor<Long> timeStampCaptor = ArgumentCaptor.forClass(Long.class);
-
-        final long DATA_TIMESTAMP = System.currentTimeMillis() + 1000000000;
-        Space space = new Space();
-        space.setCapacity(10);
-        space.setMaxCapacity(20);
-        space.setUsed(5);
-        Generation gen = new Generation();
-        gen.setName("foobar");
-        gen.setSpaces(new Space[] { space });
-        VmMemoryStat stat = new VmMemoryStat();
-        stat.setTimeStamp(DATA_TIMESTAMP);
-        stat.setGenerations(new Generation[] { gen });
-
-        when(memoryStatDao.getLatestVmMemoryStats(isA(VmRef.class), anyLong())).thenReturn(Arrays.asList(stat));
-
-        Runnable timerAction = controller.getCollector();
-
-        timerAction.run();
-        timerAction.run();
-
-        verify(memoryStatDao, times(2)).getLatestVmMemoryStats(isA(VmRef.class), timeStampCaptor.capture());
-
-        long timeStamp1 = timeStampCaptor.getAllValues().get(0);
-        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp1);
-
-        long timeStamp2 = timeStampCaptor.getAllValues().get(1);
-        assertTimeStampIsAround(DATA_TIMESTAMP, timeStamp2);
-    }
-
-    @Test
-    public void testTimerFetchesMemoryDataDeltaOnlyEvenWithNoData() {
-        ArgumentCaptor<Long> timeStampCaptor = ArgumentCaptor.forClass(Long.class);
-
-        Runnable timerAction = controller.getCollector();
-
-        timerAction.run();
-        timerAction.run();
-
-        verify(memoryStatDao, times(2)).getLatestVmMemoryStats(isA(VmRef.class), timeStampCaptor.capture());
-
-        long timeStamp1 = timeStampCaptor.getAllValues().get(0);
-        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp1);
-
-        long timeStamp2 = timeStampCaptor.getAllValues().get(1);
-        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp2);
-    }
-
-    private void assertTimeStampIsAround(long expected, long actual) {
-        assertTrue(actual <= expected + 1000);
-        assertTrue(actual >= expected - 1000);
-    }
-}
--- a/client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/PayloadTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +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.client.stats.memory.core;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
-
-public class PayloadTest {
-
-    @Test
-    public void testClone() {
-        
-        StatsModel model = new StatsModel();
-        model.setName("fluffModel");
-        model.setRange(100);
-        model.addData(500, 2.0);
-        model.addData(501, 2.1);
-        
-        Payload source = new Payload();
-        source.setCapacity(10.0);
-        source.setName("fluff");
-        source.setCapacityUnit(Scale.GiB);
-        source.setMaxCapacity(100.0);
-        source.setMaxUsed(5.0);
-        source.setUsed(3.0);
-        source.setUsedUnit(Scale.MiB);
-        source.setModel(model);
-        source.setTooltip("fluffTooltip");
-        
-        Payload cloned = source.clone();
-        assertNotSame(cloned, source);
-        
-        assertEquals(source.getName(), cloned.getName());
-        assertEquals(source.getCapacity(), cloned.getCapacity(), 0);
-        assertEquals(source.getCapacityUnit(), cloned.getCapacityUnit());
-        assertEquals(source.getMaxCapacity(), cloned.getMaxCapacity(), 0);
-        assertEquals(source.getMaxUsed(), cloned.getMaxUsed(), 0);
-        assertEquals(source.getTooltip(), cloned.getTooltip());
-        assertEquals(source.getUsed(), cloned.getUsed(), 0);
-        assertEquals(source.getUsedUnit(), cloned.getUsedUnit());
-        assertNotSame(source.getModel(), cloned.getModel());
-
-        assertEquals(source.getModel().getName(), cloned.getModel().getName());
-    }
-}
--- a/client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/RangeModelTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +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.client.stats.memory.core;
-
-import static org.junit.Assert.*;
-import junit.framework.Assert;
-
-import org.junit.Test;
-
-public class RangeModelTest {
-
-    @Test
-    public void testSameRange() {
-        RangeModel model = new RangeModel();
-        
-        model.setMaximum(10);
-        model.setMinimum(0);
-        model.setValue(5);
-        
-        model.setMaxNormalized(10);
-        model.setMinNormalized(0);
-        
-        
-        Assert.assertEquals((int) model.getValue(), model.getValueNormalized());
-    }
-
-    @Test
-    public void testDoubleRange() {
-        RangeModel model = new RangeModel();
-        
-        model.setMaximum(10);
-        model.setMinimum(0);
-        model.setValue(5);
-        
-        model.setMaxNormalized(20);
-        model.setMinNormalized(0);
-        
-        
-        Assert.assertEquals(10, model.getValueNormalized());
-    }
-    
-    @Test
-    public void testRanges() {
-        RangeModel model = new RangeModel();
-        
-        model.setMaximum(10);
-        model.setMinimum(0);
-        model.setValue(5);
-        
-        model.setMaxNormalized(40);
-        model.setMinNormalized(0);
-                
-        Assert.assertEquals(20, model.getValueNormalized());
-        
-        model.setMaxNormalized(60);
-        model.setMinNormalized(0);
-                
-        Assert.assertEquals(30, model.getValueNormalized());
-                
-        model.setMaxNormalized(200);
-        model.setMinNormalized(100);
-                
-        Assert.assertEquals(150, model.getValueNormalized());
-        
-        model.setMaximum(100);
-        model.setMinimum(0);
-        model.setValue(50);
-        
-        model.setMaxNormalized(1);
-        model.setMinNormalized(0);
-                
-        Assert.assertEquals(1, model.getValueNormalized());
-        
-        model.setValue(49);
-        Assert.assertEquals(0, model.getValueNormalized());
-        
-        model.setMaximum(1.0);
-        model.setMinimum(0.0);
-        model.setValue(0.5);
-        
-        model.setMaxNormalized(100);
-        model.setMinNormalized(0);
-        
-        Assert.assertEquals(50, model.getValueNormalized());
-        
-        model.setValue(0.72);
-        Assert.assertEquals(72, model.getValueNormalized());
-    }
-}
--- a/client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/StatsModelTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +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.client.stats.memory.core;
-
-import static org.junit.Assert.*;
-
-import org.jfree.data.time.TimeSeries;
-import org.junit.Test;
-
-public class StatsModelTest {
-
-    @Test
-    public void testClone() {
-        
-        StatsModel source = new StatsModel();
-        source.setName("fluffModel");
-        source.setRange(100);
-        source.addData(500, 2.0);
-        source.addData(501, 2.1);
-        
-        StatsModel cloned = source.clone();
-        
-        assertNotSame(cloned, source);
-        
-        assertEquals(source.getName(), cloned.getName());
-     
-        assertNotSame(cloned.getDataSet(), source.getDataSet());
-        
-        for (Object series : cloned.getDataSet().getItems()) {
-            assertTrue(source.getDataSet().getItems().contains(series));
-        }
-        
-        assertEquals(cloned.getDataSet().getItemCount(),
-                     source.getDataSet().getItemCount());
-    }
-
-}
--- a/client/memory-stats-panel/core/src/test/java/com/redhat/thermostat/client/stats/memory/core/locale/TranslateTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +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.client.stats.memory.core.locale;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Properties;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.client.stats.memory.core.locale.LocaleResources;
-import com.redhat.thermostat.common.locale.Translate;
-
-public class TranslateTest {
-
-    private Locale lang;
-
-    @Before
-    public void setUp() {
-        this.lang = Locale.getDefault();
-        Locale.setDefault(Locale.US);
-    }
-
-    @After
-    public void tearDown() {
-        Locale.setDefault(lang);
-    }
-
-    @Test
-    public void verifyTranslationsAreThere() throws IOException {
-
-        String stringsResource = "/" + LocaleResources.RESOURCE_BUNDLE.replace(".", "/") + ".properties";
-
-        Properties props = new Properties();
-        props.load(getClass().getResourceAsStream(stringsResource));
-
-        Assert.assertEquals(LocaleResources.values().length, props.values().size());
-        Translate<LocaleResources> t = LocaleResources.createLocalizer();
-        Assert.assertEquals("Missing translation!", t.localize(LocaleResources.RESOURCE_MISSING));
-        Assert.assertEquals("Memory", t.localize(LocaleResources.VM_INFO_TAB_MEMORY));
-    }
-}
--- a/client/memory-stats-panel/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-
- 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.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.redhat.thermostat</groupId>
-    <artifactId>thermostat-client</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>thermostat-osgi-memory-stats-panel</artifactId>
-  <packaging>pom</packaging>
-
-  <name>Thermostat Client VM Memory Monitor plugin</name>
-
-  <modules>
-    <module>core</module>
-    <module>swing</module>
-  </modules>
-
-</project>
--- a/client/memory-stats-panel/swing/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <artifactId>thermostat-osgi-memory-stats-panel</artifactId>
-    <groupId>com.redhat.thermostat</groupId>
-    <version>0.5.0-SNAPSHOT</version>
-  </parent>
-  <artifactId>thermostat-osgi-memory-stats-panel-swing</artifactId>
-  <packaging>bundle</packaging>
-  <name>Thermostat Client VM Memory Monitor Swing plugin</name>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Private-Package>
-                com.redhat.thermostat.client.stats.memory.swing
-            </Private-Package>
-            <Bundle-Activator>com.redhat.thermostat.client.stats.memory.swing.MemoryStatsPanelActivator</Bundle-Activator>
-            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-SymbolicName>com.redhat.thermostat.client.stats.memory.swing</Bundle-SymbolicName>
-            <!-- Do not autogenerate uses clauses in Manifests -->
-            <_nouses>true</_nouses>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.easytesting</groupId>
-      <artifactId>fest-swing</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>net.java.openjdk.cacio</groupId>
-      <artifactId>cacio-tta</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.compendium</artifactId>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.jfree</groupId>
-      <artifactId>jfreechart</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-osgi-memory-stats-panel-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-client-swing</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-gc-remote-collector-client-swing</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-        
-  </dependencies>
-</project>
--- a/client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryGraphPanel.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +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.client.stats.memory.swing;
-
-import java.awt.Dimension;
-import java.beans.Transient;
-
-import javax.swing.BoxLayout;
-import javax.swing.JPanel;
-
-import com.redhat.thermostat.client.stats.memory.core.MemoryMeter;
-import com.redhat.thermostat.client.stats.memory.core.Payload;
-
-@SuppressWarnings("serial")
-class MemoryGraphPanel extends JPanel {
-
-    private MemoryMeter meter;
-    
-    /**
-     * Create the panel.
-     */
-    public MemoryGraphPanel() {
-        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
-        meter = new MemoryMeter();
-        add(meter);
-    }
-    
-    public void setMemoryGraphProperties(Payload region) {
-
-        meter.getPrimaryModel().setMinimum(0);
-        meter.getPrimaryModel().setMaximum(region.getMaxUsed());
-        meter.getPrimaryModel().setValue(region.getUsed());
-        
-        meter.getSecondaryModel().setMinimum(0);
-        meter.getSecondaryModel().setMaximum(region.getMaxCapacity());
-        meter.getSecondaryModel().setValue(region.getCapacity());
-        
-        meter.setToolTipText(region.getTooltip());
-        
-        meter.setPrimaryScaleUnit(region.getUsedUnit().toString());
-        meter.setSecondayScaleUnit(region.getCapacityUnit().toString());
-        
-        meter.setStats(region.getModel());
-    }
-    
-    @Override
-    @Transient
-    public Dimension getPreferredSize() {
-        return meter.getPreferredSize();
-    }
-}
--- a/client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsPanelActivator.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +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.client.stats.memory.swing;
-
-import java.util.Map;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-import com.redhat.thermostat.client.core.VmInformationService;
-import com.redhat.thermostat.client.stats.memory.core.MemoryStatsService;
-import com.redhat.thermostat.client.stats.memory.core.MemoryStatsViewProvider;
-import com.redhat.thermostat.common.ApplicationService;
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.MultipleServiceTracker.Action;
-import com.redhat.thermostat.common.dao.AgentInfoDAO;
-import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
-import com.redhat.thermostat.gc.remote.common.GCRequest;
-
-public class MemoryStatsPanelActivator implements BundleActivator {
-
-    private MultipleServiceTracker tracker;
-    private ServiceRegistration memoryStatRegistration;
-
-    @Override
-    public void start(final BundleContext context) throws Exception {
-        MemoryStatsViewProvider provider = new SwingMemoryStatsViewProvider();
-        context.registerService(MemoryStatsViewProvider.class.getName(), provider, null);
-        
-        Class<?>[] deps = new Class<?>[] {
-            ApplicationService.class,
-            VmMemoryStatDAO.class,
-            GCRequest.class,
-            AgentInfoDAO.class,
-        };
-
-        tracker = new MultipleServiceTracker(context, deps, new Action() {
-            
-            @Override
-            public void dependenciesUnavailable() {
-                memoryStatRegistration.unregister();
-                memoryStatRegistration = null;
-            }
-
-            @Override
-            public void dependenciesAvailable(Map<String, Object> services) {
-                VmMemoryStatDAO memoryStatDao = (VmMemoryStatDAO) services.get(VmMemoryStatDAO.class.getName());
-                AgentInfoDAO agentDAO = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
-                GCRequest gcRequest = (GCRequest) services.get(GCRequest.class.getName());
-                ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
-
-                MemoryStatsService impl = new MemoryStatsService(appSvc, memoryStatDao, agentDAO, gcRequest);
-                memoryStatRegistration = context.registerService(VmInformationService.class.getName(), impl , null);
-            }
-        });
-        tracker.open();
-
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        tracker.close();
-    }
-}
--- a/client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/MemoryStatsViewImpl.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,168 +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.client.stats.memory.swing;
-
-import java.awt.Component;
-import java.awt.Dimension;
-import java.beans.Transient;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
-
-import com.redhat.thermostat.client.core.views.BasicView;
-import com.redhat.thermostat.client.stats.memory.core.MemoryStatsView;
-import com.redhat.thermostat.client.stats.memory.core.Payload;
-import com.redhat.thermostat.client.swing.SwingComponent;
-import com.redhat.thermostat.client.swing.components.HeaderPanel;
-import com.redhat.thermostat.client.ui.ComponentVisibleListener;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.gc.remote.client.common.RequestGCAction;
-import com.redhat.thermostat.gc.remote.client.swing.ToolbarGCButton;
-import com.redhat.thermostat.gc.remote.common.command.GCCommand;
-
-public class MemoryStatsViewImpl extends MemoryStatsView implements SwingComponent {
-
-    private static final long REPAINT_DELAY = 500;
-    private long lastRepaint;
-    
-    private HeaderPanel visiblePanel;
-    private JPanel realPanel;
-    
-    private final Map<String, MemoryGraphPanel> regions;
-    
-    private RequestGCAction toobarButtonAction;
-    
-    private Dimension preferredSize;
-    
-    public MemoryStatsViewImpl() {
-        super();
-        visiblePanel = new HeaderPanel();
-        regions = new HashMap<>();
- 
-        preferredSize = new Dimension(0, 0);
-        
-        visiblePanel.setHeader("Memory Regions");
-
-        visiblePanel.addHierarchyListener(new ComponentVisibleListener() {
-            @Override
-            public void componentShown(Component component) {
-                notifier.fireAction(Action.VISIBLE);
-            }
-
-            @Override
-            public void componentHidden(Component component) {
-                notifier.fireAction(Action.HIDDEN);
-            }
-        });
-
-        realPanel = new JPanel();
-        realPanel.setLayout(new BoxLayout(realPanel, BoxLayout.Y_AXIS));
-        visiblePanel.setContent(realPanel);
-        
-        toobarButtonAction = new RequestGCAction();
-        visiblePanel.addToolBarButton(new ToolbarGCButton(toobarButtonAction));
-    }
-    
-    @Transient
-    public Dimension getPreferredSize() {
-        return new Dimension(preferredSize);
-    }
-    
-    @Override
-    public void updateRegion(final Payload region) {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                MemoryGraphPanel memoryGraphPanel = regions.get(region.getName());
-                memoryGraphPanel.setMemoryGraphProperties(region);
-            }
-        });
-    }
-    
-    @Override
-    public void addGCActionListener(ActionListener<GCCommand> listener) {
-        toobarButtonAction.addActionListener(listener);
-    }
-    
-    @Override
-    public void addRegion(final Payload region) {
-
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                MemoryGraphPanel memoryGraphPanel = new MemoryGraphPanel();
-                
-                realPanel.add(memoryGraphPanel);
-                realPanel.add(Box.createRigidArea(new Dimension(5,5)));
-                regions.put(region.getName(), memoryGraphPanel);
-                
-                // components are stacked up vertically in this panel
-                Dimension memoryGraphPanelMinSize = memoryGraphPanel.getMinimumSize();
-                preferredSize.height += memoryGraphPanelMinSize.height + 5;
-                if (preferredSize.width < (memoryGraphPanelMinSize.width + 5)) {
-                    preferredSize.width = memoryGraphPanelMinSize.width + 5;
-                }
-
-                updateRegion(region);
-                realPanel.revalidate();
-            }
-        });
-    }
-
-    @Override
-    public Component getUiComponent() {
-        return visiblePanel;
-    }
-
-    @Override
-    public void requestRepaint() {
-        // really only repaint every REPAINT_DELAY milliseconds
-        long now = System.currentTimeMillis();
-        if (now - lastRepaint > REPAINT_DELAY) {
-            visiblePanel.repaint();
-            lastRepaint = System.currentTimeMillis();
-        }
-    }
-    
-    public BasicView getView() {
-        return this;
-    }
-}
--- a/client/memory-stats-panel/swing/src/main/java/com/redhat/thermostat/client/stats/memory/swing/SwingMemoryStatsViewProvider.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +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.client.stats.memory.swing;
-
-import com.redhat.thermostat.client.stats.memory.core.MemoryStatsView;
-import com.redhat.thermostat.client.stats.memory.core.MemoryStatsViewProvider;
-
-public class SwingMemoryStatsViewProvider implements MemoryStatsViewProvider {
-
-    @Override
-    public MemoryStatsView createView() {
-        return new MemoryStatsViewImpl();
-    }
-
-}
--- a/client/memory-stats-panel/swing/src/main/resources/com/redhat/thermostat/client/stats/memory/locale/strings.properties	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-RESOURCE_MISSING = Missing translation!
-VM_INFO_TAB_MEMORY = Memory
\ No newline at end of file
--- a/client/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -61,7 +61,6 @@
   <modules>
     <module>core</module>
     <module>heapdumper</module>
-    <module>memory-stats-panel</module>
     <module>living-vm-filter</module>
     <module>command</module>
     <module>swing</module>
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/MainWindow.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/MainWindow.java	Mon Dec 10 15:05:37 2012 +0100
@@ -103,6 +103,7 @@
 import com.redhat.thermostat.client.swing.components.EdtHelper;
 import com.redhat.thermostat.client.swing.components.HtmlTextBuilder;
 import com.redhat.thermostat.client.swing.components.StatusBar;
+import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu;
 import com.redhat.thermostat.client.swing.internal.MainView;
 import com.redhat.thermostat.client.swing.internal.components.DecoratedDefaultMutableTreeNode;
 import com.redhat.thermostat.client.swing.views.SearchFieldSwingView;
@@ -326,7 +327,7 @@
 
     private ActionNotifier<Action> actionNotifier = new ActionNotifier<>(this);
 
-    private JPopupMenu vmContextMenu;
+    private ThermostatPopupMenu vmContextMenu;
     private StatusBar statusBar;
     
     private final DefaultMutableTreeNode publishedRoot =
@@ -501,7 +502,7 @@
     }
 
     private void registerContextActionListener(JTree agentVmTree2) {
-        vmContextMenu = new JPopupMenu();
+        vmContextMenu = new ThermostatPopupMenu();
         agentVmTree2.addMouseListener(new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HeaderPanel.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/HeaderPanel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -179,7 +179,7 @@
         hasButtons = true;
     }
     
-    class PreferencesPopup extends JPopupMenu {
+    class PreferencesPopup extends ThermostatPopupMenu {
         JMenuItem preferencesMenu;
         public PreferencesPopup() {
             // TODO: localize
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ThermostatPopupMenu.java	Mon Dec 10 15:05:37 2012 +0100
@@ -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.client.swing.components;
+
+import java.awt.Window;
+
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+
+@SuppressWarnings("serial")
+public class ThermostatPopupMenu extends JPopupMenu {
+    
+    @Override
+    public void setVisible(boolean visible) {
+        
+        if (TranslucencyUtils.TRANSLUCENT && !visible) {
+            setTranslucencyLevel(TranslucencyUtils.OPAQUE);
+        }
+        
+        super.setVisible(visible);
+        
+        if (TranslucencyUtils.TRANSLUCENT && visible) {
+            setTranslucencyLevel(TranslucencyUtils.TRANSPARENCY);
+        }
+    }
+    
+    private void setTranslucencyLevel(float level) {
+        try {
+            Window ourWindow = SwingUtilities.getWindowAncestor(this);
+            ourWindow.setOpacity(level);
+            
+        } catch (Throwable ignore) {}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TranslucencyUtils.java	Mon Dec 10 15:05:37 2012 +0100
@@ -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.client.swing.components;
+
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.GraphicsDevice.WindowTranslucency;
+
+class TranslucencyUtils {
+    public static final boolean TRANSLUCENT;
+    public static final float TRANSPARENCY;
+    public static final float OPAQUE = 1.0f;
+
+    static {
+        ThermostatPopupMenu.setDefaultLightWeightPopupEnabled(false);
+
+        boolean translucent = !Boolean.getBoolean("com.redhat.thermostat.popup.opaque");
+        if (translucent) {
+            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+            GraphicsDevice gd = ge.getDefaultScreenDevice();
+            translucent = gd.isWindowTranslucencySupported(WindowTranslucency.PERPIXEL_TRANSLUCENT);
+        }
+        
+        float transparency = 0.90f;
+        String transparencyProp = System.getProperty("com.redhat.thermostat.popup.transparency");
+        if (transparencyProp != null) {
+            try {
+                transparency = Float.parseFloat(transparencyProp);
+                if (transparency > 1 || transparency < 0) {
+                    translucent = false;
+                }
+            } catch (Throwable ignore) {}
+        }
+        
+        TRANSLUCENT = translucent;
+        TRANSPARENCY = transparency;
+    }
+}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ValueField.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/ValueField.java	Mon Dec 10 15:05:37 2012 +0100
@@ -38,6 +38,7 @@
 
 import javax.swing.JEditorPane;
 import javax.swing.UIManager;
+import javax.swing.text.DefaultCaret;
 
 /**
  * A custom swing component meant for showing values. Use it like you would use
@@ -54,6 +55,17 @@
         setForeground(UIManager.getColor("Label.foreground"));
         setFont(UIManager.getFont("Label.font"));
         setEditable(false);
+
+        /*
+         * The default caret update policy forces any scroll pane this
+         * component is added to to scroll so that this component is visible.
+         * Normally, the caret is placed in the last instance of this
+         * component created which is normally at the bottom of a scroll pane.
+         * This forces the scroll pane to scroll to the bottom, unexpectedly.
+         * This field is not meant to be editable in the first place so this
+         * behaviour makes no sense; turn off the scroll updates.
+         */
+        ((DefaultCaret) getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
     }
 
 }
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Main.java	Mon Dec 10 15:05:37 2012 +0100
@@ -121,8 +121,12 @@
                     }
                 }
 
+                // Thermostat JPopupMenu instances should all be
+                // ThermostatPopupmenu, so this is redundant, but done in case
+                // some client code doesn't use the internal popup
+                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+                
                 // TODO: move them in an appropriate place
-                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
                 UIManager.getDefaults().put("OptionPane.buttonOrientation", SwingConstants.RIGHT);
                 UIManager.getDefaults().put("OptionPane.isYesLast", true);
                 UIManager.getDefaults().put("OptionPane.sameSizeButtons", true);
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/HostInformationPanel.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/views/HostInformationPanel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -43,7 +43,6 @@
 import javax.swing.JTabbedPane;
 import javax.swing.SwingUtilities;
 
-import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.HostInformationView;
 import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.client.swing.SwingComponent;
--- a/common/command/src/main/java/com/redhat/thermostat/common/command/Response.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/common/command/src/main/java/com/redhat/thermostat/common/command/Response.java	Mon Dec 10 15:05:37 2012 +0100
@@ -36,20 +36,31 @@
 
 package com.redhat.thermostat.common.command;
 
-
-
-
 public class Response implements Message {
 
     // TODO add parameter support to provide more information in some of these types.
     public enum ResponseType implements MessageType {
-        PONG,      // Just here as response to PING.
-        OK,        // Request has been acknowledged and completed agent-side.
-        NOK,       // Request has been acknowledged and refused agent-side.
-        NOOP,      // Request has been acknowledged, but no action deemed necessary agent-side.
-        ERROR,     // An error on the agent side.
-        EXCEPTION, // Exception caught by channel handler.  Agent-side status unknown.
-        AUTH_FAILED; // When authentication fails in SecureStorage.
+        /** Request has been acknowledged and completed agent-side */
+        OK,
+
+        /** Request has been acknowledged and refused agent-side. */
+        NOK,
+
+        /**
+         * Request has been acknowledged, but no action deemed necessary
+         * agent-side.
+         */
+        NOOP,
+
+        /**
+         * An error occurred. The status of the request is not known.
+         */
+        ERROR,
+
+	/**
+         * When authentication fails in SecureStorage.
+	 */
+	AUTH_FAILED;
     }
 
     ResponseType type;
--- a/common/command/src/test/java/com/redhat/thermostat/common/command/ResponseTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/common/command/src/test/java/com/redhat/thermostat/common/command/ResponseTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -47,8 +47,8 @@
 
     @Test
     public void testGetType() {
-        Response r = new Response(ResponseType.PONG);
-        assertEquals(ResponseType.PONG, r.getType());
+        Response r = new Response(ResponseType.OK);
+        assertEquals(ResponseType.OK, r.getType());
         r = new Response(ResponseType.ERROR);
         assertEquals(ResponseType.ERROR, r.getType());
     }
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/LoggingUtils.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/LoggingUtils.java	Mon Dec 10 15:05:37 2012 +0100
@@ -170,6 +170,7 @@
                 @SuppressWarnings("rawtypes")
                 Class clazz = ClassLoader.getSystemClassLoader().loadClass(clazzName);
                 Handler handler = (Handler)clazz.newInstance();
+                handler.setLevel(root.getLevel());
                 root.addHandler(handler);
             } catch (Exception e) {
                 System.err.print("Could not load log-handler '" + clazzName + "'");
--- a/common/core/src/test/java/com/redhat/thermostat/common/utils/LoggingUtilsTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/common/core/src/test/java/com/redhat/thermostat/common/utils/LoggingUtilsTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -85,7 +85,20 @@
         assertEquals(SimpleFormatter.class.getName(), handler.getFormatter().getClass().getName());
         assertEquals(Level.WARNING.getName(), rootLogger.getLevel().getName());
         assertEquals(Level.OFF.getName(), mongodbLogger.getLevel().getName());
-        
+    }
+    
+    @Test
+    public void testHandlersHaveSameLogLevelAsRoot() throws Exception {
+        LoggingUtils.loadConfig(GLOBAL_CONFIG);
+        Logger rootLogger = logManager.getLogger(LoggingUtils.ROOTNAME);
+        FileHandler handler = (FileHandler)getLogHandler(rootLogger, FileHandler.class);
+        assertEquals(Level.WARNING, rootLogger.getLevel());
+        assertEquals(rootLogger.getLevel(), handler.getLevel());
+        LoggingUtils.loadConfig(USER_CONFIG);
+        rootLogger = logManager.getLogger(LoggingUtils.ROOTNAME);
+        ConsoleHandler handler2 = (ConsoleHandler)getLogHandler(rootLogger, ConsoleHandler.class);
+        assertEquals(Level.FINEST, rootLogger.getLevel());
+        assertEquals(rootLogger.getLevel(), handler2.getLevel());
     }
     
     @Test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/client.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,12 @@
+# This file is only used in a Web storage setup and is entirely optional.
+# It allows for a user to specify a thermostat specific keystore to be used
+# for TLS certificate validation in addition to the default as described in:
+# http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#X509TrustManager
+
+# If you'd like to use a keystore file in addition to defaults uncomment the
+# following line:
+#KEYSTORE_FILE=/path/to/thermostat.keystore
+
+# The password for the keystore file. If none is provided the empty password
+# is assumed. Only used if KEYSTORE_FILE was specified.
+#KEYSTORE_PASSWORD=nopassword
\ No newline at end of file
--- a/distribution/config/commands/gui.properties	Mon Dec 10 14:30:36 2012 +0100
+++ b/distribution/config/commands/gui.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -24,6 +24,8 @@
           thermostat-vm-cpu-client-swing-@project.version@.jar, \
           thermostat-vm-gc-client-core-@project.version@.jar, \
           thermostat-vm-gc-client-swing-@project.version@.jar, \
+          thermostat-vm-memory-client-core-@project.version@.jar, \
+          thermostat-vm-memory-client-swing-@project.version@.jar, \
           thermostat-client-heapdumper-core-@project.version@.jar, \
           thermostat-client-heapdumper-swing-@project.version@.jar, \
           thermostat-killvm-client-swing-@project.version@.jar, \
@@ -31,8 +33,6 @@
           thermostat-vm-classstat-client-swing-@project.version@.jar, \
           thermostat-osgi-living-vm-filter-core-@project.version@.jar, \
           thermostat-osgi-living-vm-filter-swing-@project.version@.jar, \
-          thermostat-osgi-memory-stats-panel-core-@project.version@.jar, \
-          thermostat-osgi-memory-stats-panel-swing-@project.version@.jar, \
           thermostat-laf-@project.version@.jar, \
           thermostat-thread-collector-@project.version@.jar, \
           thermostat-thread-client-swing-@project.version@.jar, \
--- a/distribution/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ b/distribution/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -128,6 +128,14 @@
                 </resource>
                 <resource>
                   <directory>config</directory>
+                  <targetPath>etc</targetPath>
+                  <filtering>true</filtering>
+                  <includes>
+                    <include>client.properties</include>
+                  </includes>
+                </resource>
+                <resource>
+                  <directory>config</directory>
                   <targetPath>storage</targetPath>
                   <filtering>true</filtering>
                   <includes>
@@ -409,7 +417,7 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-osgi-memory-stats-panel-swing</artifactId>
+      <artifactId>thermostat-vm-memory-client-swing</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
--- a/eclipse/com.redhat.thermostat.eclipse/META-INF/MANIFEST.MF	Mon Dec 10 14:30:36 2012 +0100
+++ b/eclipse/com.redhat.thermostat.eclipse/META-INF/MANIFEST.MF	Mon Dec 10 15:05:37 2012 +0100
@@ -9,8 +9,8 @@
 Bundle-ActivationPolicy: lazy
 Require-Bundle: org.eclipse.core.runtime,
  org.eclipse.ui
-Import-Package: com.redhat.thermostat.client.core.views,
- com.redhat.thermostat.client.core.controllers,
+Import-Package: com.redhat.thermostat.client.core.controllers,
+ com.redhat.thermostat.client.core.views,
  com.redhat.thermostat.client.locale,
  com.redhat.thermostat.client.ui,
  com.redhat.thermostat.common,
@@ -21,9 +21,7 @@
  com.redhat.thermostat.host.overview.client.locale,
  com.redhat.thermostat.storage.config,
  com.redhat.thermostat.storage.core,
- com.redhat.thermostat.storage.model,
- com.redhat.thermostat.web.client,
- com.redhat.thermostat.web.common
+ com.redhat.thermostat.storage.model
 Export-Package: com.redhat.thermostat.eclipse,
  com.redhat.thermostat.eclipse.internal;x-friends:="com.redhat.thermostat.eclipse.test,com.redhat.thermostat.eclipse.test.ui",
  com.redhat.thermostat.eclipse.internal.controllers;x-friends:="com.redhat.thermostat.eclipse.test,com.redhat.thermostat.eclipse.test.ui",
--- a/host-overview/client-swing/src/main/java/com/redhat/thermostat/host/overview/client/swing/HostOverviewPanel.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/host-overview/client-swing/src/main/java/com/redhat/thermostat/host/overview/client/swing/HostOverviewPanel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -42,6 +42,7 @@
 import javax.swing.GroupLayout;
 import javax.swing.GroupLayout.Alignment;
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.LayoutStyle.ComponentPlacement;
 import javax.swing.SwingUtilities;
@@ -63,6 +64,7 @@
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
     private JPanel visiblePanel;
+    private JScrollPane scrollPane;
 
     private final ValueField hostname = new ValueField("${hostname}");
     private final ValueField cpuModel = new ValueField("${cpu-model}");
@@ -202,7 +204,7 @@
 
     @Override
     public Component getUiComponent() {
-        return visiblePanel;
+        return scrollPane;
     }
 
     private void initializePanel() {
@@ -304,6 +306,9 @@
         panel.add(networkTable);
         JTableHeader header = networkTable.getTableHeader();
         panel.add(header, BorderLayout.PAGE_START);
+
         visiblePanel.setLayout(gl_visiblePanel);
+
+        scrollPane = new JScrollPane(visiblePanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
     }
 }
--- a/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/internal/SwingVMKilledListener.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/internal/SwingVMKilledListener.java	Mon Dec 10 15:05:37 2012 +0100
@@ -62,11 +62,6 @@
     @Override
     public void fireComplete(Request request, Response response) {
         switch (response.getType()) {
-        case EXCEPTION:
-            logger.log(Level.SEVERE,
-                    "Exception response from kill VM request. Command channel failure?");
-            showErrorMessage(t.localize(LocaleResources.KILL_ACTION_EXCEPTION_RESPONSE_MSG));
-            break;
         case ERROR:
             String vmId = request.getParameter("vm-id");
             logger.log(Level.SEVERE,
@@ -74,7 +69,6 @@
                             + vmId);
             showErrorMessage(t.localize(LocaleResources.KILL_ACTION_ERROR_RESPONSE_MSG, vmId));
             break;
-        case PONG: // fall-through, also OK :)
         case OK:
             logger.log(Level.INFO,
                     "VM with id " + request.getParameter("vm-id")
--- a/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/locale/LocaleResources.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/killvm/client-swing/src/main/java/com/redhat/thermostat/killvm/client/locale/LocaleResources.java	Mon Dec 10 15:05:37 2012 +0100
@@ -42,7 +42,6 @@
 
     ACTION_NAME,
     ACTION_DESCRIPTION,
-    KILL_ACTION_EXCEPTION_RESPONSE_MSG,
     KILL_ACTION_ERROR_RESPONSE_MSG,
     MISSING_INFO;
 
--- a/killvm/client-swing/src/main/resources/com/redhat/thermostat/killvm/client/locale/strings.properties	Mon Dec 10 14:30:36 2012 +0100
+++ b/killvm/client-swing/src/main/resources/com/redhat/thermostat/killvm/client/locale/strings.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -1,5 +1,4 @@
 ACTION_NAME = Kill Application
 ACTION_DESCRIPTION = Kill the selected VM Process
 MISSING_INFO = Missing Information
-KILL_ACTION_EXCEPTION_RESPONSE_MSG = Channel Transport Error. Please check your configuration!
 KILL_ACTION_ERROR_RESPONSE_MSG = Failed to kill VM with pid {0}
\ No newline at end of file
--- a/killvm/client-swing/src/test/java/com/redhat/thermostat/killvm/client/internal/SwingVMKilledListenerTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/killvm/client-swing/src/test/java/com/redhat/thermostat/killvm/client/internal/SwingVMKilledListenerTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -53,15 +53,6 @@
 public class SwingVMKilledListenerTest {
     
     @Test
-    public void exceptionShowsError() {
-        ResponseActionListener listener = new ResponseActionListener();
-        Request request = mock(Request.class); 
-        Response resp = new Response(ResponseType.EXCEPTION);
-        listener.fireComplete(request, resp);
-        assertTrue(listener.isActionPerformed());
-    }
-    
-    @Test
     public void errorShowsError() {
         ResponseActionListener listener = new ResponseActionListener();
         Request request = mock(Request.class); 
--- a/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ b/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -140,6 +140,7 @@
     <module>vm-cpu</module>
     <module>vm-gc</module>
     <module>vm-classstat</module>
+    <module>vm-memory</module>
     <!-- development related modules -->
     <module>dev</module>
   </modules>
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/MongoStorageProvider.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/MongoStorageProvider.java	Mon Dec 10 15:05:37 2012 +0100
@@ -36,9 +36,6 @@
 
 package com.redhat.thermostat.storage.mongodb;
 
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageProvider;
--- a/vm-gc/remote-collector-client-common/src/main/java/com/redhat/thermostat/gc/remote/common/GCRequest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/vm-gc/remote-collector-client-common/src/main/java/com/redhat/thermostat/gc/remote/common/GCRequest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -40,6 +40,7 @@
 
 import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
 import com.redhat.thermostat.common.command.Request.RequestType;
 import com.redhat.thermostat.common.dao.AgentInfoDAO;
 import com.redhat.thermostat.common.dao.HostRef;
@@ -53,7 +54,7 @@
         this.queue = queue;
     }
         
-    public void sendGCRequestToAgent(VmRef vm, AgentInfoDAO agentDAO) {
+    public void sendGCRequestToAgent(VmRef vm, AgentInfoDAO agentDAO, RequestResponseListener responseListener) {
                 
         HostRef targetHostRef = vm.getAgent();
 
@@ -68,6 +69,8 @@
         gcRequest.setParameter(GCCommand.class.getName(), GCCommand.REQUEST_GC.name());
         gcRequest.setParameter(GCCommand.VM_ID, vm.getIdString());
         
+        gcRequest.addListener(responseListener);
+
         queue.putRequest(gcRequest);
     }
     
--- a/vm-gc/remote-collector-client-common/src/test/java/com/redhat/thermostat/gc/remote/common/GCRequestTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/vm-gc/remote-collector-client-common/src/test/java/com/redhat/thermostat/gc/remote/common/GCRequestTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -48,6 +48,7 @@
 
 import com.redhat.thermostat.client.command.RequestQueue;
 import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
 import com.redhat.thermostat.common.dao.AgentInfoDAO;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
@@ -62,6 +63,7 @@
 
     private GCRequest gcRequest;
     private Request request;
+    private RequestResponseListener listener;
     
     @Before
     public void setUp() {
@@ -70,6 +72,8 @@
 
         request = mock(Request.class);
         
+        listener = mock(RequestResponseListener.class);
+
         HostRef ref = mock(HostRef.class);        
         when(vm.getAgent()).thenReturn(ref);
         when(vm.getIdString()).thenReturn("123456");
@@ -101,7 +105,7 @@
             }
         };
         
-        gcRequest.sendGCRequestToAgent(vm, agentDAO);
+        gcRequest.sendGCRequestToAgent(vm, agentDAO, listener);
         verify(vm).getAgent();
         verify(vm).getIdString();
         
@@ -112,6 +116,7 @@
         verify(request).setReceiver(GCCommand.RECEIVER);
         verify(request).setParameter(GCCommand.class.getName(), GCCommand.REQUEST_GC.name());
         verify(request).setParameter(GCCommand.VM_ID, "123456");
+        verify(request).addListener(listener);
         
         verify(queue).putRequest(request);
     }
--- a/vm-gc/remote-collector-command/src/main/java/com/redhat/thermostat/gc/remote/command/GCCommandReceiver.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/vm-gc/remote-collector-command/src/main/java/com/redhat/thermostat/gc/remote/command/GCCommandReceiver.java	Mon Dec 10 15:05:37 2012 +0100
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
 import com.redhat.thermostat.gc.remote.command.internal.GC;
+import com.redhat.thermostat.gc.remote.command.internal.GCException;
 import com.redhat.thermostat.gc.remote.common.command.GCCommand;
 import com.redhat.thermostat.utils.management.MXBeanConnector;
 
@@ -48,18 +49,22 @@
 
     @Override
     public Response receive(Request request) {
+        Response response = new Response(ResponseType.OK);
         
         String command = request.getParameter(GCCommand.class.getName());
         switch (GCCommand.valueOf(command)) {
         case REQUEST_GC:
-            String vmId = request.getParameter(GCCommand.VM_ID);
-            MXBeanConnector connector = new MXBeanConnector(vmId);
-            new GC(connector).gc();
+            try {
+                String vmId = request.getParameter(GCCommand.VM_ID);
+                MXBeanConnector connector = new MXBeanConnector(vmId);
+                new GC(connector).gc();
+            } catch (GCException gce) {
+                response = new Response(ResponseType.ERROR);
+            }
             break;
-        
         default:
             break;
         }
-        return new Response(ResponseType.OK);
+        return response;
     }
 }
--- a/vm-gc/remote-collector-command/src/main/java/com/redhat/thermostat/gc/remote/command/internal/GC.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/vm-gc/remote-collector-command/src/main/java/com/redhat/thermostat/gc/remote/command/internal/GC.java	Mon Dec 10 15:05:37 2012 +0100
@@ -54,7 +54,8 @@
         this.connector = connector;
     }
     
-    public void gc() {
+    public void gc() throws GCException {
+        Exception exceptionInGc = null;
         boolean closeAfter = false;
         if (!connector.isAttached()) {
             closeAfter = true; 
@@ -75,12 +76,17 @@
             bean.gc();
 
         } catch (Exception ex) {
+            exceptionInGc = ex;
             logger.log(Level.SEVERE, "can't get MXBeanConnection connection", ex);
         }
         
         if (closeAfter) {
             closeConnection();
         }
+
+        if (exceptionInGc != null) {
+            throw new GCException("error performing gc", exceptionInGc);
+        }
     }
     
     private void closeConnection() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-gc/remote-collector-command/src/main/java/com/redhat/thermostat/gc/remote/command/internal/GCException.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,44 @@
+/*
+ * 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.gc.remote.command.internal;
+
+public class GCException extends Exception {
+
+    public GCException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>thermostat-vm-memory</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-vm-memory-client-core</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat VM Memory Core Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Export-Package>
+                com.redhat.thermostat.vm.memory.client.core,
+                com.redhat.thermostat.vm.memory.client.locale,
+            </Export-Package>
+            <Private-Package>
+                com.redhat.thermostat.vm.memory.client.core.internal
+            </Private-Package>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.vm.memory.client.core.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.vm.memory.client.core</Bundle-SymbolicName>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>    
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryMeter.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,433 @@
+/*
+ * 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.vm.memory.client.core;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.beans.Transient;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ColorUIResource;
+
+import sun.swing.SwingUtilities2;
+
+@SuppressWarnings({ "restriction", "serial" })
+public class MemoryMeter extends JComponent {
+    
+    // TODO the font should be customizable
+    private static final Font font = new Font("SansSerif", Font.PLAIN, 10);
+    
+    private static final int TICK_NUM = 100;
+    private static final int SMALL_TICK_NUM = 5;
+    
+    private static final int MAIN_BAR_HEIGHT = 20;
+    
+    private static final ColorUIResource MAIN_GRADIENT_TOP = new ColorUIResource(0xf1f3f1);
+    private static final ColorUIResource MAIN_BORDER_COLOR = new ColorUIResource(0xa8aca8);
+    
+    private static final ColorUIResource MAIN_BAR_BASE_COLOR_TOP = new ColorUIResource(0xbcd5ef);
+    private static final ColorUIResource MAIN_BAR_BASE_COLOR = new ColorUIResource(0x4A90D9);
+    
+    //private static final ColorUIResource STATS_BG = new ColorUIResource(0xF8F8F8);
+    private static final ColorUIResource STATS_BG = new ColorUIResource(0xFFFFFF);
+    
+    private ColorUIResource tickColor;
+    
+    private RangeModel primary;
+    private RangeModel secondary;
+    
+    private Insets boundInsets;
+    
+    private RangeModel internalSecondaryModel;
+    
+    private StatsModel primaryStats;
+    
+    private String primaryUnit;
+    private String secondaryUnit;
+
+        public void setPrimaryScaleUnit(String primaryUnit) {
+        this.primaryUnit = primaryUnit;
+    }
+    
+    public void setSecondayScaleUnit(String secondaryUnit) {
+        this.secondaryUnit = secondaryUnit;
+    }
+    
+    public StatsModel getStats() {
+        return primaryStats;
+    }
+    
+    public void setStats(StatsModel primaryStats) {
+        this.primaryStats = primaryStats;
+    }
+
+    public MemoryMeter() {
+        
+        secondaryUnit = "";
+        primaryUnit = "";
+        
+        boundInsets = new Insets(10, 10, 20, 20);
+        
+        tickColor = new ColorUIResource(0xdbdddb);
+        
+        primary = new RangeModel();
+        primary.setMinimum(0);
+        primary.setMaximum(100);
+        primary.setMinNormalized(0);
+        primary.setMaxNormalized(100);
+        
+        secondary = new RangeModel();
+        secondary.setMinimum(0);
+        secondary.setMaximum(100);
+        secondary.setMinNormalized(0);
+        secondary.setMaxNormalized(100);
+        
+        internalSecondaryModel = new RangeModel();
+    }
+    
+    public ColorUIResource getTickColor() {
+        return tickColor;
+    }
+    
+    public void setTickColor(ColorUIResource tickColor) {
+        this.tickColor = tickColor;
+    }
+    
+    public RangeModel getPrimaryModel() {
+        return primary;
+    }
+    
+    public RangeModel getSecondaryModel() {
+        return secondary;
+    }
+    
+    protected Rectangle getOuterBounds() {
+        return new Rectangle(0, 0, getWidth(), getHeight());
+    }
+    
+    protected Rectangle getBoundsWithInsets(Rectangle bounds) {
+        return new Rectangle(bounds.x + boundInsets.left,
+                             bounds.y + boundInsets.top,
+                             bounds.width  - boundInsets.right,
+                             bounds.height - boundInsets.bottom);
+    }
+        
+    /**
+     * paint the outher frame, including the light border sorrounding
+     */
+    protected void paintOuterFrame(Graphics2D graphics, Rectangle bounds) {
+
+        RoundRectangle2D frame = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width, bounds.height, 6, 6);
+
+        Paint paint = new GradientPaint(0, 0, MAIN_GRADIENT_TOP, 0, getHeight(), getBackground());
+        graphics.setPaint(paint);
+        graphics.fill(frame);
+        
+        paint = new GradientPaint(0, 0, MAIN_BORDER_COLOR, 0, getHeight(), getBackground());
+        graphics.setPaint(paint);
+        frame = new RoundRectangle2D.Float(bounds.x, bounds.y, bounds.width -1, bounds.height, 6, 6);
+        graphics.draw(frame);
+    }
+    
+    /**
+     * paint the track sorrounding the main bar
+     */
+    protected void paintMainBarTrackFill(Graphics2D graphics, Rectangle bounds) {
+        
+        Paint paint = new GradientPaint(0, 0, MAIN_GRADIENT_TOP, 0, bounds.height, Color.WHITE);
+        graphics.setPaint(paint);
+        RoundRectangle2D frame = new RoundRectangle2D.Float(0, 0, bounds.width, bounds.height, 6, 6);
+        graphics.fill(frame);
+    }
+    
+    /**
+     */
+    protected void paintMainBarTrackBorder(Graphics2D graphics, Rectangle bounds) {
+        Paint paint = new GradientPaint(0, 0, MAIN_BORDER_COLOR, getWidth(), 0, getBackground());
+        graphics.setPaint(paint);
+        
+        RoundRectangle2D frame = new RoundRectangle2D.Float(0, 0, bounds.width - 1, bounds.height, 6, 6);
+        graphics.draw(frame);
+    }
+
+    
+    /**
+     * this is the main bar, will it up to what is defined by the model
+     */
+    protected void paintMainBarFill(Graphics2D graphics, Rectangle bounds) {
+        Paint paint = new GradientPaint(0, 0, MAIN_BAR_BASE_COLOR, getWidth() * 2, 0, getBackground());
+        graphics.setPaint(paint);
+        
+        RoundRectangle2D frame =
+                new RoundRectangle2D.Float(0, 0, getPrimaryModel().getValueNormalized(),
+                                           bounds.height, 6, 6);
+        graphics.fill(frame);
+        
+        String value = String.valueOf(getPrimaryModel().getValue()) + " " + primaryUnit;
+        Rectangle2D fontBounds = font.getStringBounds(value, graphics.getFontRenderContext());
+        int width = (int) (bounds.width/2 - fontBounds.getWidth()/2) - 1;
+        
+        if (width > getPrimaryModel().getValueNormalized()) {
+            graphics.setPaint(MAIN_BAR_BASE_COLOR);
+        } else {
+            graphics.setPaint(getBackground());
+        }
+
+        int height  = (int) (bounds.height / 2 + fontBounds.getHeight()/2);
+        SwingUtilities2.drawString(this, graphics, value, width, height);
+    }
+    
+    /**
+     */
+    private void paintMainBar(Graphics2D g, Rectangle bounds) {
+        
+        Graphics2D graphics = (Graphics2D) g.create();
+                
+        graphics.translate(bounds.x, bounds.y);
+        paintMainBarTrackFill(graphics, bounds);
+        
+        paintMainBarFill(graphics, bounds);
+        
+        paintMainBarTrackBorder(graphics, bounds);
+        graphics.dispose();
+    }
+    
+    /**
+     */
+    protected void drawBottomBar(Graphics2D g, Rectangle bounds) {
+
+        Graphics2D graphics = (Graphics2D) g.create();
+        graphics.translate(bounds.x, bounds.y + bounds.height);
+        
+        drawTickMark(graphics, bounds);
+        
+        paintSecondaryBarFill(graphics, bounds);
+        
+        graphics.dispose();
+    }
+    
+    /**
+     */
+    protected void drawTickMark(Graphics2D graphics, Rectangle bounds) {
+      
+        graphics.setPaint(MAIN_BORDER_COLOR);
+        
+        int smallTop = bounds.height - 5;
+        int smallBottom = bounds.height;
+
+        int mainTop = smallTop + 20;
+        int mainBottom = smallBottom - 15;
+      
+        // the first and last tick are always big, this is the first
+        graphics.drawLine(0, mainTop, 0, mainBottom);
+      
+        // the space between the vertical lines
+        double tickSpace = ((double) bounds.width) / TICK_NUM;
+      
+        internalSecondaryModel.setMaxNormalized(bounds.width);      
+        int numTicks = 0;
+
+        for (int x = 0; x < bounds.width; x += tickSpace + 0.5) {
+            if (numTicks % SMALL_TICK_NUM == 0) {
+              
+                graphics.drawLine(x, smallTop, x, smallBottom + 5);
+
+                internalSecondaryModel.setValue(x);
+            } else {
+                graphics.drawLine(x, smallTop, x, smallBottom);
+            }
+            numTicks++;
+        }
+        
+        // that's the last
+        graphics.drawLine(bounds.width, mainTop, bounds.width, mainBottom);
+        graphics.drawLine(0, smallTop, bounds.width, smallTop);
+        
+        drawStrings(graphics, mainBottom, mainTop, bounds.width);
+    }
+    
+    protected void drawStrings(Graphics2D graphics, int top, int bottom, int right) {
+      
+      // now draw the min/max values of both side of markers      
+      // top bar min value
+      FontMetrics fm = SwingUtilities2.getFontMetrics(this, font);
+      
+      String value = String.valueOf(getPrimaryModel().getMinimum()) + " " + primaryUnit;
+      int height = top + fm.getAscent()/2;
+      SwingUtilities2.drawString(this, graphics, value, 1, height);
+      
+      value = String.valueOf(getSecondaryModel().getMinimum()) + " " + secondaryUnit;
+      height = bottom;
+      SwingUtilities2.drawString(this, graphics, value, 1, height);
+      
+      value = String.valueOf(getPrimaryModel().getMaximum()) + " " + primaryUnit;
+      height = top + fm.getAscent()/2;
+
+      int width = (int) (right - font.getStringBounds(value, graphics.getFontRenderContext()).getWidth()) - 1;
+      SwingUtilities2.drawString(this, graphics, value, width, height);
+      
+      value = String.valueOf(getSecondaryModel().getMaximum()) + " " + secondaryUnit;
+      height = bottom;
+      width = (int) (right - font.getStringBounds(value, graphics.getFontRenderContext()).getWidth()) - 1;
+      SwingUtilities2.drawString(this, graphics, value, width, height);
+      
+      // now draw the actual value for the bottom bar, the top bar is drawn in
+      // its fill method
+      value = String.valueOf(getSecondaryModel().getValue()) + " " + secondaryUnit;
+      width = right/2;
+      Rectangle2D bounds = font.getStringBounds(value, graphics.getFontRenderContext());
+      width = (int) (width - bounds.getWidth()/2) - 1;
+      SwingUtilities2.drawString(this, graphics, value, width, height);
+      RoundRectangle2D frame = new RoundRectangle2D.Double(width - 2, height - bounds.getHeight(),
+                                                          bounds.getWidth() + 4, bounds.getHeight() + 4,
+                                                          4, 4);
+      graphics.draw(frame);
+    }
+    
+    protected void paintSecondaryBarFill(Graphics2D graphics, Rectangle bounds) {
+        
+        graphics.setPaint(MAIN_BAR_BASE_COLOR_TOP);
+        graphics.drawLine(1, bounds.height, getSecondaryModel().getValueNormalized() - 1, bounds.height);
+        
+        graphics.setPaint(MAIN_BAR_BASE_COLOR);
+        graphics.fillRect(1, bounds.height + 1, getSecondaryModel().getValueNormalized(), 2);
+    }
+    
+    protected void paintStats(Graphics2D graphics, Rectangle bounds) {
+        
+        int imageWidth = bounds.width - 2;
+        if (imageWidth < 0 || bounds.height < 0) {
+            return;
+        }
+        
+        StatsModel stats = getStats();
+        drawStats(graphics, stats, bounds.x, bounds.y, imageWidth, bounds.height);
+    }
+    
+    private void drawStats(Graphics2D graphics, StatsModel stats, int x, int y, int imageWidth, int height) {
+        if (stats != null) {
+            BufferedImage image = stats.getChart(imageWidth, height, STATS_BG,
+                                                 new ColorUIResource(getForeground()));
+       
+            paintStatsLabel(graphics, image, stats.getName());
+            graphics.drawImage(image, x, y, null);
+        }
+    }
+    
+    protected void paintStatsLabel(Graphics2D graphics, BufferedImage image, String label) {
+        int height = SwingUtilities2.getFontMetrics(this, font).getAscent() + 2;
+        if (height <= 0) {
+            return;
+        }
+        
+        Graphics2D imageGraphics = (Graphics2D) image.getGraphics();
+        imageGraphics.setColor(getForeground());
+        imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        
+        SwingUtilities2.drawString(this, imageGraphics, label, 2, height);
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+
+        Graphics2D graphics = (Graphics2D) g.create();
+        graphics.setFont(font);
+        
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        
+        Rectangle outerBounds = getOuterBounds();
+        Rectangle innerBounds = getBoundsWithInsets(outerBounds);
+        Rectangle statsBound =  getBoundsWithInsets(outerBounds);
+        
+        // move the bar close to the center
+        innerBounds.height = MAIN_BAR_HEIGHT;
+        innerBounds.y = outerBounds.height/2;
+        
+        // make the stats area cover the upper portion instead
+        statsBound.height = (outerBounds.height/2) - MAIN_BAR_HEIGHT;
+        
+        resetModels(0, innerBounds.width);
+
+        // some eye candy
+        paintOuterFrame(graphics, outerBounds);
+
+        // paint the usage stats
+        paintStats(graphics, statsBound);
+        
+        // main and bottom bars
+        paintMainBar(graphics, innerBounds);
+        drawBottomBar(graphics, innerBounds);
+        
+        graphics.dispose();
+    }
+    
+    private void resetModels(int min, int max) {
+        
+        getPrimaryModel().setMaxNormalized(max);
+        getPrimaryModel().setMinNormalized(min);
+        
+        RangeModel model = getSecondaryModel();
+        model.setMaxNormalized(max);
+        model.setMinNormalized(min);
+        
+        internalSecondaryModel.setMaximum(model.getMaximum());
+        internalSecondaryModel.setMinimum(model.getMinimum());
+        internalSecondaryModel.setValue(model.getValue());
+        
+        internalSecondaryModel.setMaxNormalized(max);
+        internalSecondaryModel.setMinNormalized(0);
+    }
+    
+    @Override
+    @Transient
+    public Dimension getPreferredSize() {
+        return new Dimension(850, 150);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryStatsService.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,83 @@
+/*
+ * 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.vm.memory.client.core;
+
+import com.redhat.thermostat.client.core.VmFilter;
+import com.redhat.thermostat.client.core.VmInformationService;
+import com.redhat.thermostat.client.core.controllers.VmInformationServiceController;
+import com.redhat.thermostat.client.osgi.service.AlwaysMatchFilter;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.dao.AgentInfoDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.gc.remote.common.GCRequest;
+import com.redhat.thermostat.vm.memory.client.core.internal.MemoryStatsController;
+
+public class MemoryStatsService implements VmInformationService {
+    
+    private static final int PRIORITY = PRIORITY_MEMORY_GROUP + 40;
+    private VmFilter filter = new AlwaysMatchFilter();
+
+    private ApplicationService appSvc;
+    private VmMemoryStatDAO vmMemoryStatDao;
+    private AgentInfoDAO agentDAO;
+    private GCRequest gcRequest;
+    
+    public MemoryStatsService(ApplicationService appSvc, VmMemoryStatDAO vmMemoryStatDao, AgentInfoDAO agentDAO, GCRequest gcRequest) {
+        this.appSvc = appSvc;
+        this.vmMemoryStatDao = vmMemoryStatDao;
+        this.gcRequest = gcRequest;
+        this.agentDAO = agentDAO;
+    }
+    
+    @Override
+    public VmInformationServiceController getInformationServiceController(VmRef ref) {
+        MemoryStatsViewProvider viewProvider = OSGIUtils.getInstance().getService(MemoryStatsViewProvider.class);
+        return new MemoryStatsController(appSvc, vmMemoryStatDao, ref, viewProvider, agentDAO, gcRequest);
+    }
+
+    @Override
+    public VmFilter getFilter() {
+        return filter;
+    }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryStatsView.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,55 @@
+/*
+ * 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.vm.memory.client.core;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.gc.remote.common.command.GCCommand;
+
+public abstract class MemoryStatsView extends BasicView implements UIComponent {
+    
+    public abstract void addRegion(Payload region);
+    public abstract void updateRegion(Payload region);
+    
+    public abstract void addGCActionListener(ActionListener<GCCommand> listener);
+    
+    public abstract void requestRepaint();
+
+    public abstract void displayWarning(String string);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/MemoryStatsViewProvider.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,46 @@
+/*
+ * 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.vm.memory.client.core;
+
+import com.redhat.thermostat.client.core.views.ViewProvider;
+
+public interface MemoryStatsViewProvider extends ViewProvider {
+
+    @Override
+    public MemoryStatsView createView();
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/Payload.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,145 @@
+/*
+ * 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.vm.memory.client.core;
+
+import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
+
+public class Payload implements Cloneable {
+
+    private String name;
+    private String tooltip;
+    
+    private double capacity;
+    private double maxCapacity;
+    private double maxUsed;
+    private double used;
+    
+    private Scale usedUnit;
+    private Scale capacityUnit;
+    
+    private StatsModel model;
+    
+    public void setModel(StatsModel model) {
+        this.model = model;
+    }
+    
+    public StatsModel getModel() {
+        return model;
+    }
+    
+    public void setCapacityUnit(Scale capacityUnit) {
+        this.capacityUnit = capacityUnit;
+    }
+    
+    public Scale getCapacityUnit() {
+        return capacityUnit;
+    }
+    
+    public void setUsedUnit(Scale usedUnit) {
+        this.usedUnit = usedUnit;
+    }
+    
+    public Scale getUsedUnit() {
+        return usedUnit;
+    }
+    
+    public double getMaxCapacity() {
+        return maxCapacity;
+    }
+    
+    public void setMaxCapacity(double maxCapacity) {
+        this.maxCapacity = maxCapacity;
+    }
+    
+    public double getUsed() {
+        return used;
+    }
+    
+    public void setUsed(double used) {
+        this.used = used;
+    }
+    
+    public double getMaxUsed() {
+        return maxUsed;
+    }
+    
+    public void setMaxUsed(double maxUsed) {
+        this.maxUsed = maxUsed;
+    }
+    
+    public double getCapacity() {
+        return capacity;
+    }
+    
+    public void setCapacity(double capacity) {
+        this.capacity = capacity;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public String getTooltip() {
+        return tooltip;
+    }
+    
+    public void setTooltip(String tooltip) {
+        this.tooltip = tooltip;
+    }
+    
+    @Override
+    public Payload clone() {
+        
+        Payload copy = new Payload();
+        
+        copy.used = used;
+        copy.capacity = capacity;
+        copy.capacityUnit = capacityUnit;
+        copy.maxCapacity = maxCapacity;
+        copy.maxUsed = maxUsed;
+        copy.model = model.clone();
+        copy.name = name;
+        copy.tooltip = tooltip;
+        copy.usedUnit = usedUnit;
+        
+        return copy;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/RangeModel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,93 @@
+/*
+ * 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.vm.memory.client.core;
+
+public class RangeModel {
+
+    private int minNormalized;
+    private double min;
+    
+    private int maxNormalized;
+    private double max;
+ 
+    private double value;
+ 
+    public double getMinimum() {
+        return min;
+    }
+
+    public void setMinimum(double newMinimum) {
+        this.min = newMinimum;
+    }
+
+    public double getMaximum() {
+        return max;
+    }
+
+    public void setMaximum(double newMaximum) {
+        this.max = newMaximum;
+    }
+
+    public void setMaxNormalized(int maxNormalized) {
+        this.maxNormalized = maxNormalized;
+    }
+    
+    public void setMinNormalized(int minNormalized) {
+        this.minNormalized = minNormalized;
+    }
+    
+    public double getValue() {
+        return value;
+    }
+
+    public void setValue(double newValue) {
+        this.value = newValue;
+    }
+    
+    int getMaxNormalized() {
+        return maxNormalized;
+    }
+    
+    int getMinNormalized() {
+        return minNormalized;
+    }
+    
+    public int getValueNormalized() {
+        double normalized = ((value - min) * (maxNormalized - minNormalized)/(max - min)) + minNormalized;
+        return (int) Math.round(normalized);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/StatsModel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,181 @@
+/*
+ * 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.vm.memory.client.core;
+
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.Date;
+
+import javax.swing.plaf.ColorUIResource;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
+import org.jfree.data.time.Millisecond;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.ui.RectangleInsets;
+
+public class StatsModel implements Cloneable {
+
+    private static final String lock = new String("MemoryStatsModelLock");
+
+    private String name;
+    
+    private TimeSeries dataSet;
+    
+    public StatsModel() {
+        dataSet = new TimeSeries("");
+    }
+    
+    BufferedImage getChart(int width, int height, ColorUIResource bgColor, ColorUIResource fgColor) {
+        JFreeChart chart = createChart(bgColor, fgColor);
+        
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        chart.draw((Graphics2D) image.getGraphics(), new Rectangle2D.Double(0, 0, width, height), null);
+        
+        return image;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+    public void setName(String name) {
+        dataSet.setDescription(name);
+        this.name = name;
+    }
+    
+    public void setRange(int seconds) {
+        dataSet.setMaximumItemCount(seconds);
+    }
+    
+    public void addData(long timestamp, double value) {
+        Millisecond millisecond = new Millisecond(new Date(timestamp));
+        synchronized (lock) {
+            if (dataSet.getValue(millisecond) == null) {
+                dataSet.add(millisecond, value);
+                dataSet.removeAgedItems(true);
+            }
+        }
+    }
+    
+    @Override
+    protected StatsModel clone() {
+        
+        StatsModel model = new StatsModel();
+        model.setName(name);
+        model.setRange(dataSet.getMaximumItemCount());
+        
+        try {
+            model.dataSet = dataSet.createCopy(0, dataSet.getItemCount() - 1);
+        } catch (CloneNotSupportedException e) {
+            // ah... it's supported here...
+            e.printStackTrace();
+        }
+        
+        return model;
+    }
+    
+    /**
+     * Creates a chart.
+     *
+     * @return a chart.
+     */
+    private JFreeChart createChart(ColorUIResource bgColor, ColorUIResource fgColor) {
+
+        XYDataset priceData = null;
+        synchronized (lock) {            
+            try {
+                priceData = new TimeSeriesCollection(dataSet.createCopy(0,
+                                                     dataSet.getItemCount() - 1));
+            } catch (CloneNotSupportedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        XYPlot plot = new XYPlot();
+        
+        plot.setDomainGridlinesVisible(false);
+        plot.setDomainCrosshairVisible(false);
+        plot.setRangeGridlinesVisible(false);
+        plot.setRangeCrosshairVisible(false);
+                
+        DateAxis dateAxis = new DateAxis();
+        
+        dateAxis.setTickLabelsVisible(false);
+        dateAxis.setTickMarksVisible(false);
+        dateAxis.setAxisLineVisible(false);
+        dateAxis.setNegativeArrowVisible(false);
+        dateAxis.setPositiveArrowVisible(false);
+        dateAxis.setVisible(false);
+        
+        NumberAxis numberAxis = new NumberAxis();
+        numberAxis.setTickLabelsVisible(false);
+        numberAxis.setTickMarksVisible(false);
+        numberAxis.setAxisLineVisible(false);
+        numberAxis.setNegativeArrowVisible(false);
+        numberAxis.setPositiveArrowVisible(false);
+        numberAxis.setVisible(false);
+        numberAxis.setAutoRangeIncludesZero(false);
+        
+        plot.setDomainAxis(dateAxis);
+        plot.setRangeAxis(numberAxis);
+        plot.setDataset(priceData);
+        
+        plot.setInsets(new RectangleInsets(-1, -1, 0, 0));
+        
+        plot.setRenderer(new StandardXYItemRenderer(StandardXYItemRenderer.LINES));
+        plot.setBackgroundPaint(bgColor);
+        
+        JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false);
+        
+        plot.getRenderer().setSeriesPaint(0, fgColor);
+        chart.setAntiAlias(true);
+        chart.setBorderVisible(false);
+        
+        return chart;
+    }
+    
+    TimeSeries getDataSet() {
+        return dataSet;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/internal/Activator.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,94 @@
+/*
+ * 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.vm.memory.client.core.internal;
+
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.core.VmInformationService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.dao.AgentInfoDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.gc.remote.common.GCRequest;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsService;
+
+public class Activator implements BundleActivator {
+
+    private MultipleServiceTracker tracker;
+    private ServiceRegistration memoryStatRegistration;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        Class<?>[] deps = new Class<?>[] {
+            ApplicationService.class,
+            VmMemoryStatDAO.class,
+            GCRequest.class,
+            AgentInfoDAO.class,
+        };
+
+        tracker = new MultipleServiceTracker(context, deps, new Action() {
+            
+            @Override
+            public void dependenciesUnavailable() {
+                memoryStatRegistration.unregister();
+                memoryStatRegistration = null;
+            }
+
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                VmMemoryStatDAO memoryStatDao = (VmMemoryStatDAO) services.get(VmMemoryStatDAO.class.getName());
+                AgentInfoDAO agentDAO = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
+                GCRequest gcRequest = (GCRequest) services.get(GCRequest.class.getName());
+                ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
+
+                MemoryStatsService impl = new MemoryStatsService(appSvc, memoryStatDao, agentDAO, gcRequest);
+                memoryStatRegistration = context.registerService(VmInformationService.class.getName(), impl , null);
+            }
+        });
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/core/internal/MemoryStatsController.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,261 @@
+/*
+ * 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.vm.memory.client.core.internal;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.core.controllers.VmInformationServiceController;
+import com.redhat.thermostat.client.core.views.BasicView.Action;
+import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.dao.AgentInfoDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
+import com.redhat.thermostat.gc.remote.common.GCRequest;
+import com.redhat.thermostat.gc.remote.common.command.GCCommand;
+import com.redhat.thermostat.storage.model.VmMemoryStat;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsView;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsViewProvider;
+import com.redhat.thermostat.vm.memory.client.core.Payload;
+import com.redhat.thermostat.vm.memory.client.core.StatsModel;
+import com.redhat.thermostat.vm.memory.client.locale.LocaleResources;
+
+public class MemoryStatsController implements VmInformationServiceController {
+
+    private static final Translate<LocaleResources> translate = LocaleResources.createLocalizer();
+
+    private final MemoryStatsView view;
+    private final VmMemoryStatDAO vmDao;
+   
+    private final VmRef ref;
+    private final Timer timer;
+    
+    private final Map<String, Payload> regions;
+    
+    private VMCollector collector;
+    
+    class VMCollector implements Runnable {
+
+        private long desiredUpdateTimeStamp = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
+
+        @Override
+        public void run() {
+            List<VmMemoryStat> vmInfo = vmDao.getLatestVmMemoryStats(ref, desiredUpdateTimeStamp);
+            for (VmMemoryStat memoryStats: vmInfo) {
+                Generation[] generations = memoryStats.getGenerations();
+                
+                for (Generation generation : generations) {
+                    Space[] spaces = generation.getSpaces();
+                    for (Space space: spaces) {
+                        Payload payload = regions.get(space.getName());
+                        if (payload == null) {
+                            payload = new Payload();
+                            payload.setName(space.getName());
+                        }
+
+                        Scale usedScale = normalizeScale(space.getUsed(), space.getCapacity());
+                        double used = Scale.convertTo(usedScale, space.getUsed(), 100);
+                        double maxUsed = Scale.convertTo(usedScale, space.getCapacity(), 100);
+                        
+                        payload.setUsed(used);
+                        payload.setMaxUsed(maxUsed);
+                        payload.setUsedUnit(usedScale);
+                        
+                        Scale maxScale = normalizeScale(space.getCapacity(), space.getMaxCapacity());
+                        double capacity = Scale.convertTo(maxScale, space.getCapacity(), 100);
+                        double maxCapacity = Scale.convertTo(maxScale, space.getMaxCapacity(), 100);
+                        
+                        payload.setCapacity(capacity);
+                        payload.setMaxCapacity(maxCapacity);
+                        payload.setCapacityUnit(maxScale);
+                        
+                        String tooltip = space.getName() + ": used: " + used + " " + usedScale +
+                                ", capacity: " + capacity + " " + maxScale +
+                                ", max capacity: " + maxCapacity + " " + maxScale;
+                        
+                        payload.setTooltip(tooltip);
+                        
+                        StatsModel model = payload.getModel();
+                        if (model == null) {
+                            model = new StatsModel();
+                            model.setName(space.getName());
+                            model.setRange(3600);
+                        }
+                        
+                        // normalize this always in the same unit
+                        model.addData(memoryStats.getTimeStamp(),
+                                      Scale.convertTo(Scale.MiB, space.getUsed(), 100));
+                        
+                        payload.setModel(model);
+                        if (regions.containsKey(space.getName())) {
+                            view.updateRegion(payload.clone());
+                        } else {
+                            view.addRegion(payload.clone());
+                            regions.put(space.getName(), payload);
+                        }
+                        
+                        view.requestRepaint();
+                        desiredUpdateTimeStamp = Math.max(desiredUpdateTimeStamp, memoryStats.getTimeStamp());
+                    }
+                }
+            }
+        }
+    }
+    
+    public MemoryStatsController(ApplicationService appSvc, final VmMemoryStatDAO vmMemoryStatDao,
+                                 final VmRef ref, MemoryStatsViewProvider viewProvider,
+                                 final AgentInfoDAO agentDAO, final GCRequest gcRequest) {
+        
+        regions = new HashMap<>();
+        this.ref = ref;
+        vmDao = vmMemoryStatDao;
+        view = viewProvider.createView();
+        
+        timer = appSvc.getTimerFactory().createTimer();
+        
+        collector = new VMCollector();
+        timer.setAction(collector);
+        
+        timer.setInitialDelay(0);
+        timer.setDelay(1000);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+
+        view.addActionListener(new ActionListener<Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch(actionEvent.getActionId()) {
+                    case HIDDEN:
+                        stop();
+                        break;
+                        
+                    case VISIBLE:
+                        start();
+                        break;
+                        
+                    default:
+                        throw new NotImplementedException("unknown event: " + actionEvent.getActionId());
+                }
+            }
+        });
+        
+        view.addGCActionListener(new ActionListener<GCCommand>() {
+            @Override
+            public void actionPerformed(ActionEvent<GCCommand> actionEvent) {
+                RequestResponseListener listener = new RequestResponseListener() {
+                    @Override
+                    public void fireComplete(Request request, Response response) {
+                        if (response.getType() == ResponseType.ERROR) {
+                            view.displayWarning(translate.localize(
+                                    LocaleResources.ERROR_PERFORMING_GC,
+                                    ref.getAgent().getAgentId(),
+                                    ref.getIdString()));
+                        }
+                    }
+                };
+                gcRequest.sendGCRequestToAgent(ref, agentDAO, listener);
+            }
+        });
+    }
+    
+    // for testing
+    VMCollector getCollector() {
+        return collector;
+    };
+    
+    Map<String, Payload> getRegions() {
+        return regions;
+    }
+    
+    private Scale normalizeScale(long min, long max) {
+        // FIXME: this is very dumb and very inefficient
+        // needs cleanup
+        Scale minScale = Scale.getScale(min);
+        Scale maxScale = Scale.getScale(max);
+        
+        Scale[] scales = Scale.values();
+        int maxID = 0;
+        int minID = 0;
+        for (int i = 0; i < scales.length; i++) {
+            if (scales[i] == minScale) {
+                minID = i;
+            }
+            if (scales[i] == maxScale) {
+                maxID = i;
+            }
+        }
+        while (maxID - minID >= 2) {
+            minID++;
+        }
+        return scales[minID];
+    }
+    
+    private void start() {
+        timer.start();
+    }
+
+    private void stop() {
+        timer.stop();
+    }
+
+    @Override
+    public String getLocalizedName() {
+        Translate<LocaleResources> t = LocaleResources.createLocalizer();
+        return t.localize(LocaleResources.VM_INFO_TAB_MEMORY);
+    }
+
+    @Override
+    public UIComponent getView() {
+        return (UIComponent) view;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/java/com/redhat/thermostat/vm/memory/client/locale/LocaleResources.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,54 @@
+/*
+ * 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.vm.memory.client.locale;
+
+import com.redhat.thermostat.common.locale.Translate;
+
+public enum LocaleResources {
+
+    VM_INFO_TAB_MEMORY,
+    ERROR_PERFORMING_GC,
+
+    RESOURCE_MISSING;
+    
+    public static final String RESOURCE_BUNDLE =
+            "com.redhat.thermostat.vm.memory.client.locale.strings";
+    
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/main/resources/com/redhat/thermostat/vm/memory/client/locale/strings.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,4 @@
+RESOURCE_MISSING = Missing translation!
+VM_INFO_TAB_MEMORY = Memory
+
+ERROR_PERFORMING_GC = Error performing Garbage Collection (agent: {0}, vm: {1})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/core/PayloadTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,83 @@
+/*
+ * 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.vm.memory.client.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.utils.DisplayableValues.Scale;
+
+public class PayloadTest {
+
+    @Test
+    public void testClone() {
+        
+        StatsModel model = new StatsModel();
+        model.setName("fluffModel");
+        model.setRange(100);
+        model.addData(500, 2.0);
+        model.addData(501, 2.1);
+        
+        Payload source = new Payload();
+        source.setCapacity(10.0);
+        source.setName("fluff");
+        source.setCapacityUnit(Scale.GiB);
+        source.setMaxCapacity(100.0);
+        source.setMaxUsed(5.0);
+        source.setUsed(3.0);
+        source.setUsedUnit(Scale.MiB);
+        source.setModel(model);
+        source.setTooltip("fluffTooltip");
+        
+        Payload cloned = source.clone();
+        assertNotSame(cloned, source);
+        
+        assertEquals(source.getName(), cloned.getName());
+        assertEquals(source.getCapacity(), cloned.getCapacity(), 0);
+        assertEquals(source.getCapacityUnit(), cloned.getCapacityUnit());
+        assertEquals(source.getMaxCapacity(), cloned.getMaxCapacity(), 0);
+        assertEquals(source.getMaxUsed(), cloned.getMaxUsed(), 0);
+        assertEquals(source.getTooltip(), cloned.getTooltip());
+        assertEquals(source.getUsed(), cloned.getUsed(), 0);
+        assertEquals(source.getUsedUnit(), cloned.getUsedUnit());
+        assertNotSame(source.getModel(), cloned.getModel());
+
+        assertEquals(source.getModel().getName(), cloned.getModel().getName());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/core/RangeModelTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,122 @@
+/*
+ * 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.vm.memory.client.core;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class RangeModelTest {
+
+    @Test
+    public void testSameRange() {
+        RangeModel model = new RangeModel();
+        
+        model.setMaximum(10);
+        model.setMinimum(0);
+        model.setValue(5);
+        
+        model.setMaxNormalized(10);
+        model.setMinNormalized(0);
+        
+        
+        Assert.assertEquals((int) model.getValue(), model.getValueNormalized());
+    }
+
+    @Test
+    public void testDoubleRange() {
+        RangeModel model = new RangeModel();
+        
+        model.setMaximum(10);
+        model.setMinimum(0);
+        model.setValue(5);
+        
+        model.setMaxNormalized(20);
+        model.setMinNormalized(0);
+        
+        
+        Assert.assertEquals(10, model.getValueNormalized());
+    }
+    
+    @Test
+    public void testRanges() {
+        RangeModel model = new RangeModel();
+        
+        model.setMaximum(10);
+        model.setMinimum(0);
+        model.setValue(5);
+        
+        model.setMaxNormalized(40);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(20, model.getValueNormalized());
+        
+        model.setMaxNormalized(60);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(30, model.getValueNormalized());
+                
+        model.setMaxNormalized(200);
+        model.setMinNormalized(100);
+                
+        Assert.assertEquals(150, model.getValueNormalized());
+        
+        model.setMaximum(100);
+        model.setMinimum(0);
+        model.setValue(50);
+        
+        model.setMaxNormalized(1);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(1, model.getValueNormalized());
+        
+        model.setValue(49);
+        Assert.assertEquals(0, model.getValueNormalized());
+        
+        model.setMaximum(1.0);
+        model.setMinimum(0.0);
+        model.setValue(0.5);
+        
+        model.setMaxNormalized(100);
+        model.setMinNormalized(0);
+        
+        Assert.assertEquals(50, model.getValueNormalized());
+        
+        model.setValue(0.72);
+        Assert.assertEquals(72, model.getValueNormalized());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/core/StatsModelTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -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.vm.memory.client.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class StatsModelTest {
+
+    @Test
+    public void testClone() {
+        
+        StatsModel source = new StatsModel();
+        source.setName("fluffModel");
+        source.setRange(100);
+        source.addData(500, 2.0);
+        source.addData(501, 2.1);
+        
+        StatsModel cloned = source.clone();
+        
+        assertNotSame(cloned, source);
+        
+        assertEquals(source.getName(), cloned.getName());
+     
+        assertNotSame(cloned.getDataSet(), source.getDataSet());
+        
+        for (Object series : cloned.getDataSet().getItems()) {
+            assertTrue(source.getDataSet().getItems().contains(series));
+        }
+        
+        assertEquals(cloned.getDataSet().getItemCount(),
+                     source.getDataSet().getItemCount());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/core/internal/ActivatorTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,97 @@
+/*
+ * 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.vm.memory.client.core.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.core.VmInformationService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.dao.AgentInfoDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.gc.remote.common.GCRequest;
+import com.redhat.thermostat.test.StubBundleContext;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsService;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyActivatorDoesNotRegisterServiceOnMissingDeps() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertEquals(0, context.getAllServices().size());
+        assertNotSame(1, context.getServiceListeners().size());
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+    }
+
+    @Test
+    public void verifyActivatorRegistersServices() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        VmMemoryStatDAO vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
+        ApplicationService appSvc = mock(ApplicationService.class);
+        GCRequest gcRequest = mock(GCRequest.class);
+        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
+
+        context.registerService(VmMemoryStatDAO.class, vmMemoryStatDAO, null);
+        context.registerService(ApplicationService.class, appSvc, null);
+        context.registerService(GCRequest.class, gcRequest, null);
+        context.registerService(AgentInfoDAO.class, agentInfoDAO, null);
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertTrue(context.isServiceRegistered(VmInformationService.class.getName(), MemoryStatsService.class));
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+        assertEquals(4, context.getAllServices().size());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/core/internal/MemoryStatsControllerTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,290 @@
+/*
+ * 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.vm.memory.client.core.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.dao.AgentInfoDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.gc.remote.common.GCRequest;
+import com.redhat.thermostat.gc.remote.common.command.GCCommand;
+import com.redhat.thermostat.storage.model.VmMemoryStat;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsView;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsViewProvider;
+import com.redhat.thermostat.vm.memory.client.core.Payload;
+
+public class MemoryStatsControllerTest {
+
+    private Generation[] generations = new Generation[2];
+    
+    private VmMemoryStatDAO memoryStatDao;
+    private MemoryStatsView view;
+    private Timer timer;
+    
+    private ActionListener<MemoryStatsView.Action> viewListener;
+    private ActionListener<GCCommand> gcActionListener;
+
+    private MemoryStatsController controller;
+    
+    private Space canary;
+
+    private AgentInfoDAO agentDAO;
+    private GCRequest gcRequest;
+    
+    private VmRef ref;
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Before
+    public void setUp() {
+        timer = mock(Timer.class);
+        ArgumentCaptor<Runnable> actionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(actionCaptor.capture());
+        
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+        ApplicationService appSvc = mock(ApplicationService.class);
+        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
+        
+        List<VmMemoryStat> vmInfo = new ArrayList<>();
+        
+        for (int i = 0; i < 2; i++) {
+            Generation generation = new Generation();
+            generation.setName("fluff" + i);
+            VmMemoryStat.Space[] spaces = new VmMemoryStat.Space[2 + (1 - i)]; 
+            for (int j = 0; j < 2; j++) {
+                Space space = new Space();
+                space.setName("fluffer" + i + j);
+                space.setUsed(100);
+                space.setCapacity(1000);
+                space.setMaxCapacity(10000);
+                spaces[j] = space;
+            }
+            if (i == 0) {
+                // special payload because the others have all the same values
+                canary = new Space();
+                canary.setName("canary");
+                canary.setUsed(1);
+                canary.setCapacity(2);
+                canary.setMaxCapacity(3);
+                spaces[2] = canary;
+            }
+            generation.setSpaces(spaces);
+            generations[i] = generation;
+        }
+        
+        long timestamp = 1;
+        int vmID = 1;
+        for (int i = 0; i < 5; i++) {
+            VmMemoryStat vmMemory = new VmMemoryStat(timestamp++, vmID, generations);
+            vmInfo.add(vmMemory);
+        }
+        
+        memoryStatDao = mock(VmMemoryStatDAO.class);
+        when(memoryStatDao.getLatestVmMemoryStats(any(VmRef.class), anyLong())).thenReturn(vmInfo);
+        
+        view = mock(MemoryStatsView.class);
+        MemoryStatsViewProvider viewProvider = mock(MemoryStatsViewProvider.class);
+        when(viewProvider.createView()).thenReturn(view);
+        
+        ArgumentCaptor<ActionListener> viewArgumentCaptor =
+                ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addActionListener(viewArgumentCaptor.capture());
+
+        ArgumentCaptor<ActionListener> gcArgumentCaptor =
+                ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addGCActionListener(gcArgumentCaptor.capture());
+        
+        ref = mock(VmRef.class);
+        
+        agentDAO = mock(AgentInfoDAO.class);
+        gcRequest = mock(GCRequest.class);
+        
+        controller = new MemoryStatsController(appSvc, memoryStatDao, ref, viewProvider, agentDAO, gcRequest);
+        
+        viewListener = viewArgumentCaptor.getValue();
+        gcActionListener = gcArgumentCaptor.getValue();
+    }
+    
+    @Test
+    public void testStartStopTimer() {
+        viewListener.actionPerformed(new ActionEvent<>(view, MemoryStatsView.Action.VISIBLE));
+
+        verify(timer).start();
+        verify(timer).setSchedulingType(SchedulingType.FIXED_RATE);
+
+        viewListener.actionPerformed(new ActionEvent<>(view, MemoryStatsView.Action.HIDDEN));
+
+        verify(timer).stop();
+    }
+
+    @Test
+    public void testGCInvoked() {
+        gcActionListener.actionPerformed(new ActionEvent<>(view, GCCommand.REQUEST_GC));
+        verify(gcRequest).sendGCRequestToAgent(eq(ref), eq(agentDAO), isA(RequestResponseListener.class));
+    }
+    
+    @Test
+    public void testPayloadContainSpaces() {
+        MemoryStatsController.VMCollector collettor = controller.getCollector();
+        collettor.run();
+        
+        Map<String, Payload> regions = controller.getRegions();
+        assertEquals(5, regions.size());
+        
+        assertTrue(regions.containsKey("fluffer00"));
+        assertTrue(regions.containsKey("fluffer01"));
+        assertTrue(regions.containsKey("fluffer10"));
+        assertTrue(regions.containsKey("fluffer11"));
+        
+        assertTrue(regions.containsKey("canary"));
+    }
+    
+    @Test
+    public void testValues() {
+        MemoryStatsController.VMCollector collettor = controller.getCollector();
+        collettor.run();
+        
+        Map<String, Payload> regions = controller.getRegions();
+
+        Payload payload = regions.get("fluffer00");
+        assertEquals("fluffer00", payload.getName());
+        assertEquals(10000, payload.getMaxCapacity(), 0);
+        assertEquals(1000, payload.getCapacity(), 0);
+        assertEquals(100, payload.getUsed(), 0);
+        
+        payload = regions.get("canary");
+        assertEquals("canary", payload.getName());
+        assertEquals(3, payload.getMaxCapacity(), 0);
+        assertEquals(2, payload.getCapacity(), 0);
+        assertEquals(1, payload.getUsed(), 0);
+        
+        // the value above all ensure the same scale is used
+        String tooltip = payload.getName() + ": used: " + payload.getUsed() + " " +
+                         payload.getUsedUnit() + ", capacity: " +
+                         payload.getCapacity() + " " + payload.getUsedUnit() +
+                         ", max capacity: " + payload.getMaxCapacity() + " " +
+                         payload.getUsedUnit();
+        
+        assertEquals(tooltip, payload.getTooltip());
+    }
+    
+
+    @Test
+    public void testTimerFetchesMemoryDataDeltaOnly() {
+        ArgumentCaptor<Long> timeStampCaptor = ArgumentCaptor.forClass(Long.class);
+
+        final long DATA_TIMESTAMP = System.currentTimeMillis() + 1000000000;
+        Space space = new Space();
+        space.setCapacity(10);
+        space.setMaxCapacity(20);
+        space.setUsed(5);
+        Generation gen = new Generation();
+        gen.setName("foobar");
+        gen.setSpaces(new Space[] { space });
+        VmMemoryStat stat = new VmMemoryStat();
+        stat.setTimeStamp(DATA_TIMESTAMP);
+        stat.setGenerations(new Generation[] { gen });
+
+        when(memoryStatDao.getLatestVmMemoryStats(isA(VmRef.class), anyLong())).thenReturn(Arrays.asList(stat));
+
+        Runnable timerAction = controller.getCollector();
+
+        timerAction.run();
+        timerAction.run();
+
+        verify(memoryStatDao, times(2)).getLatestVmMemoryStats(isA(VmRef.class), timeStampCaptor.capture());
+
+        long timeStamp1 = timeStampCaptor.getAllValues().get(0);
+        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp1);
+
+        long timeStamp2 = timeStampCaptor.getAllValues().get(1);
+        assertTimeStampIsAround(DATA_TIMESTAMP, timeStamp2);
+    }
+
+    @Test
+    public void testTimerFetchesMemoryDataDeltaOnlyEvenWithNoData() {
+        ArgumentCaptor<Long> timeStampCaptor = ArgumentCaptor.forClass(Long.class);
+
+        Runnable timerAction = controller.getCollector();
+
+        timerAction.run();
+        timerAction.run();
+
+        verify(memoryStatDao, times(2)).getLatestVmMemoryStats(isA(VmRef.class), timeStampCaptor.capture());
+
+        long timeStamp1 = timeStampCaptor.getAllValues().get(0);
+        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp1);
+
+        long timeStamp2 = timeStampCaptor.getAllValues().get(1);
+        assertTimeStampIsAround(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1), timeStamp2);
+    }
+
+    private void assertTimeStampIsAround(long expected, long actual) {
+        assertTrue(actual <= expected + 1000);
+        assertTrue(actual >= expected - 1000);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-core/src/test/java/com/redhat/thermostat/vm/memory/client/locale/LocaleResourcesTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,53 @@
+/*
+ * 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.vm.memory.client.locale;
+
+import com.redhat.thermostat.test.locale.AbstractLocaleResourcesTest;
+
+public class LocaleResourcesTest extends AbstractLocaleResourcesTest<LocaleResources> {
+
+    @Override
+    protected Class<LocaleResources> getEnumClass() {
+        return LocaleResources.class;
+    }
+
+    @Override
+    protected String getResourceBundle() {
+        return LocaleResources.RESOURCE_BUNDLE;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>thermostat-vm-memory</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-vm-memory-client-swing</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat VM Memory Swing Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Private-Package>
+              com.redhat.thermostat.vm.memory.client.swing
+            </Private-Package>
+            <Bundle-Activator>com.redhat.thermostat.vm.memory.client.swing.Activator</Bundle-Activator>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-SymbolicName>com.redhat.thermostat.vm.memory.client.swing</Bundle-SymbolicName>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easytesting</groupId>
+      <artifactId>fest-swing</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>net.java.openjdk.cacio</groupId>
+      <artifactId>cacio-tta</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-memory-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-swing</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-gc-remote-collector-client-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-gc-remote-collector-client-swing</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+        
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/src/main/java/com/redhat/thermostat/vm/memory/client/swing/Activator.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,56 @@
+/*
+ * 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.vm.memory.client.swing;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsViewProvider;
+
+public class Activator implements BundleActivator {
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+        MemoryStatsViewProvider provider = new SwingMemoryStatsViewProvider();
+        // Automatically unregistered on Activator.stop
+        context.registerService(MemoryStatsViewProvider.class.getName(), provider, null);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/src/main/java/com/redhat/thermostat/vm/memory/client/swing/MemoryGraphPanel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,85 @@
+/*
+ * 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.vm.memory.client.swing;
+
+import java.awt.Dimension;
+import java.beans.Transient;
+
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+
+import com.redhat.thermostat.vm.memory.client.core.MemoryMeter;
+import com.redhat.thermostat.vm.memory.client.core.Payload;
+
+@SuppressWarnings("serial")
+class MemoryGraphPanel extends JPanel {
+
+    private MemoryMeter meter;
+    
+    /**
+     * Create the panel.
+     */
+    public MemoryGraphPanel() {
+        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+        meter = new MemoryMeter();
+        add(meter);
+    }
+    
+    public void setMemoryGraphProperties(Payload region) {
+
+        meter.getPrimaryModel().setMinimum(0);
+        meter.getPrimaryModel().setMaximum(region.getMaxUsed());
+        meter.getPrimaryModel().setValue(region.getUsed());
+        
+        meter.getSecondaryModel().setMinimum(0);
+        meter.getSecondaryModel().setMaximum(region.getMaxCapacity());
+        meter.getSecondaryModel().setValue(region.getCapacity());
+        
+        meter.setToolTipText(region.getTooltip());
+        
+        meter.setPrimaryScaleUnit(region.getUsedUnit().toString());
+        meter.setSecondayScaleUnit(region.getCapacityUnit().toString());
+        
+        meter.setStats(region.getModel());
+    }
+    
+    @Override
+    @Transient
+    public Dimension getPreferredSize() {
+        return meter.getPreferredSize();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/src/main/java/com/redhat/thermostat/vm/memory/client/swing/MemoryStatsViewImpl.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,174 @@
+/*
+ * 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.vm.memory.client.swing;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.beans.Transient;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.ui.ComponentVisibleListener;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.gc.remote.client.common.RequestGCAction;
+import com.redhat.thermostat.gc.remote.client.swing.ToolbarGCButton;
+import com.redhat.thermostat.gc.remote.common.command.GCCommand;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsView;
+import com.redhat.thermostat.vm.memory.client.core.Payload;
+
+public class MemoryStatsViewImpl extends MemoryStatsView implements SwingComponent {
+
+    private static final long REPAINT_DELAY = 500;
+    private long lastRepaint;
+    
+    private HeaderPanel visiblePanel;
+    private JPanel realPanel;
+    
+    private final Map<String, MemoryGraphPanel> regions;
+    
+    private RequestGCAction toobarButtonAction;
+    
+    private Dimension preferredSize;
+    
+    public MemoryStatsViewImpl() {
+        super();
+        visiblePanel = new HeaderPanel();
+        regions = new HashMap<>();
+ 
+        preferredSize = new Dimension(0, 0);
+        
+        visiblePanel.setHeader("Memory Regions");
+
+        visiblePanel.addHierarchyListener(new ComponentVisibleListener() {
+            @Override
+            public void componentShown(Component component) {
+                notifier.fireAction(Action.VISIBLE);
+            }
+
+            @Override
+            public void componentHidden(Component component) {
+                notifier.fireAction(Action.HIDDEN);
+            }
+        });
+
+        realPanel = new JPanel();
+        realPanel.setLayout(new BoxLayout(realPanel, BoxLayout.Y_AXIS));
+        visiblePanel.setContent(realPanel);
+        
+        toobarButtonAction = new RequestGCAction();
+        visiblePanel.addToolBarButton(new ToolbarGCButton(toobarButtonAction));
+    }
+    
+    @Transient
+    public Dimension getPreferredSize() {
+        return new Dimension(preferredSize);
+    }
+    
+    @Override
+    public void updateRegion(final Payload region) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MemoryGraphPanel memoryGraphPanel = regions.get(region.getName());
+                memoryGraphPanel.setMemoryGraphProperties(region);
+            }
+        });
+    }
+    
+    @Override
+    public void addGCActionListener(ActionListener<GCCommand> listener) {
+        toobarButtonAction.addActionListener(listener);
+    }
+    
+    @Override
+    public void addRegion(final Payload region) {
+
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                MemoryGraphPanel memoryGraphPanel = new MemoryGraphPanel();
+                
+                realPanel.add(memoryGraphPanel);
+                realPanel.add(Box.createRigidArea(new Dimension(5,5)));
+                regions.put(region.getName(), memoryGraphPanel);
+                
+                // components are stacked up vertically in this panel
+                Dimension memoryGraphPanelMinSize = memoryGraphPanel.getMinimumSize();
+                preferredSize.height += memoryGraphPanelMinSize.height + 5;
+                if (preferredSize.width < (memoryGraphPanelMinSize.width + 5)) {
+                    preferredSize.width = memoryGraphPanelMinSize.width + 5;
+                }
+
+                updateRegion(region);
+                realPanel.revalidate();
+            }
+        });
+    }
+
+    @Override
+    public void displayWarning(String string) {
+        JOptionPane.showMessageDialog(visiblePanel, string, "Warning", JOptionPane.WARNING_MESSAGE);
+    }
+
+    @Override
+    public Component getUiComponent() {
+        return visiblePanel;
+    }
+
+    @Override
+    public void requestRepaint() {
+        // really only repaint every REPAINT_DELAY milliseconds
+        long now = System.currentTimeMillis();
+        if (now - lastRepaint > REPAINT_DELAY) {
+            visiblePanel.repaint();
+            lastRepaint = System.currentTimeMillis();
+        }
+    }
+    
+    public BasicView getView() {
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/src/main/java/com/redhat/thermostat/vm/memory/client/swing/SwingMemoryStatsViewProvider.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,49 @@
+/*
+ * 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.vm.memory.client.swing;
+
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsView;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsViewProvider;
+
+public class SwingMemoryStatsViewProvider implements MemoryStatsViewProvider {
+
+    @Override
+    public MemoryStatsView createView() {
+        return new MemoryStatsViewImpl();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/client-swing/src/test/java/com/redhat/thermostat/vm/memory/client/swing/ActivatorTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,59 @@
+/*
+ * 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.vm.memory.client.swing;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.test.StubBundleContext;
+import com.redhat.thermostat.vm.memory.client.core.MemoryStatsViewProvider;
+import com.redhat.thermostat.vm.memory.client.swing.Activator;
+import com.redhat.thermostat.vm.memory.client.swing.SwingMemoryStatsViewProvider;
+
+public class ActivatorTest {
+
+    @Test
+    public void verifyStartRegistersViewProvider() throws Exception {
+        StubBundleContext ctx = new StubBundleContext();
+        Activator activator = new Activator();
+        activator.start(ctx);
+        assertTrue(ctx.isServiceRegistered(MemoryStatsViewProvider.class.getName(), SwingMemoryStatsViewProvider.class));
+        assertEquals(1, ctx.getAllServices().size());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-memory/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat</artifactId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-vm-memory</artifactId>
+  <packaging>pom</packaging>
+
+  <name>Thermostat VM Memory plugin</name>
+
+  <modules>
+    <module>client-core</module>
+    <module>client-swing</module>
+  </modules>
+
+</project>
--- a/vm-overview/client-swing/src/main/java/com/redhat/thermostat/vm/overview/client/swing/VmOverviewPanel.java	Mon Dec 10 14:30:36 2012 +0100
+++ b/vm-overview/client-swing/src/main/java/com/redhat/thermostat/vm/overview/client/swing/VmOverviewPanel.java	Mon Dec 10 15:05:37 2012 +0100
@@ -37,10 +37,16 @@
 package com.redhat.thermostat.vm.overview.client.swing;
 
 import java.awt.Component;
+import java.awt.Point;
+import java.awt.Rectangle;
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.Components;
@@ -58,6 +64,7 @@
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
     
     private HeaderPanel visiblePanel;
+    private JScrollPane container;
 
     private final ChangeableText pid = new ChangeableText("");
     private final ChangeableText startTimeStamp = new ChangeableText("");
@@ -69,6 +76,7 @@
     private final ChangeableText vmNameAndVersion = new ChangeableText("");
     private final ChangeableText vmArguments = new ChangeableText("");
 
+
     public VmOverviewPanel() {
         super();
         initializePanel();
@@ -181,6 +189,9 @@
         SimpleTable simpleTable = new SimpleTable();
         JPanel table = simpleTable.createTable(allSections);
         table.setBorder(Components.smallBorder());
-        visiblePanel.setContent(table);
+
+        container = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+
+        visiblePanel.setContent(container);
     }
 }
--- a/web/client/pom.xml	Mon Dec 10 14:30:36 2012 +0100
+++ b/web/client/pom.xml	Mon Dec 10 15:05:37 2012 +0100
@@ -122,10 +122,10 @@
           <instructions>
             <Bundle-SymbolicName>com.redhat.thermostat.web.client</Bundle-SymbolicName>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.web.client.Activator</Bundle-Activator>
-            <Export-Package>
-              com.redhat.thermostat.web.client
-            </Export-Package>
+            <Bundle-Activator>com.redhat.thermostat.web.client.internal.Activator</Bundle-Activator>
+            <Private-Package>
+              com.redhat.thermostat.web.client.internal
+            </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
           </instructions>
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/Activator.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +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.web.client;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.StorageProvider;
-
-public class Activator implements BundleActivator {
-
-    private ServiceRegistration reg;
-    
-    @Override
-    public void start(BundleContext context) throws Exception {
-        WebStorageProvider storage = new WebStorageProvider();
-        this.reg = context.registerService(StorageProvider.class.getName(), storage, null);
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        reg.unregister();
-    }
-
-}
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/WebCursor.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +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.web.client;
-
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.model.Pojo;
-
-class WebCursor<T extends Pojo> implements Cursor<T> {
-
-    private T[] data;
-    private int index;
-
-    WebCursor(T[] data) {
-        this.data = data;
-        index = 0;
-    }
-
-    @Override
-    public boolean hasNext() {
-        return index < data.length;
-    }
-
-    @Override
-    public T next() {
-        T result = data[index];
-        index++;
-        return result;
-    }
-
-}
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorage.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,531 +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.web.client;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.lang.reflect.Array;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.NameValuePair;
-import org.apache.http.StatusLine;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.entity.mime.MultipartEntity;
-import org.apache.http.entity.mime.content.InputStreamBody;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.http.util.EntityUtils;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.storage.core.AuthToken;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Connection;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.SecureStorage;
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.StorageException;
-import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.model.AgentIdPojo;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.web.common.ThermostatGSONConverter;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebQuery;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-public class WebStorage implements Storage, SecureStorage {
-
-    private static class CloseableHttpEntity implements Closeable, HttpEntity {
-
-        private HttpEntity entity;
-
-        CloseableHttpEntity(HttpEntity entity) {
-            this.entity = entity;
-        }
-
-        @Override
-        public void consumeContent() throws IOException {
-            entity.consumeContent();
-        }
-
-        @Override
-        public InputStream getContent() throws IOException, IllegalStateException {
-            return entity.getContent();
-        }
-
-        @Override
-        public Header getContentEncoding() {
-            return entity.getContentEncoding();
-        }
-
-        @Override
-        public long getContentLength() {
-            return entity.getContentLength();
-        }
-
-        @Override
-        public Header getContentType() {
-            return entity.getContentType();
-        }
-
-        @Override
-        public boolean isChunked() {
-            return entity.isChunked();
-        }
-
-        @Override
-        public boolean isRepeatable() {
-            return entity.isRepeatable();
-        }
-
-        @Override
-        public boolean isStreaming() {
-            return entity.isStreaming();
-        }
-
-        @Override
-        public void writeTo(OutputStream out) throws IOException {
-            entity.writeTo(out);
-        }
-
-        @Override
-        public void close() {
-            try {
-                EntityUtils.consume(entity);
-            } catch (IOException ex) {
-                throw new StorageException(ex);
-            }
-        }
-
-    }
-
-    private final class WebConnection extends Connection {
-        WebConnection() {
-            connected = true;
-        }
-        @Override
-        public void disconnect() {
-            connected = false;
-            fireChanged(ConnectionStatus.DISCONNECTED);
-        }
-
-        @Override
-        public void connect() {
-            try {
-                initAuthentication(httpClient);
-                ping();
-                connected = true;
-                fireChanged(ConnectionStatus.CONNECTED);
-            } catch (Exception ex) {
-                fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
-            }
-        }
-
-        @Override
-        public void setUrl(String url) {
-            super.setUrl(url);
-            endpoint = url;
-        }
-
-        @Override
-        public String getUrl() {
-            return endpoint;
-        }
-    }
-
-    private static class WebDataStream extends InputStream {
-
-        private CloseableHttpEntity entity;
-        private InputStream content;
-
-        WebDataStream(CloseableHttpEntity entity) {
-            this.entity = entity;
-            try {
-                content = entity.getContent();
-            } catch (IllegalStateException | IOException e) {
-                throw new StorageException(e);
-            }
-        }
-
-        @Override
-        public void close() throws IOException {
-            content.close();
-            entity.close();
-        }
-
-        @Override
-        public int read() throws IOException {
-            return content.read();
-        }
-
-        @Override
-        public int available() throws IOException {
-            return content.available();
-        }
-
-        @Override
-        public void mark(int readlimit) {
-            content.mark(readlimit);
-        }
-
-        @Override
-        public boolean markSupported() {
-            return content.markSupported();
-        }
-
-        @Override
-        public int read(byte[] b) throws IOException {
-            return content.read(b);
-        }
-
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            return content.read(b, off, len);
-        }
-
-        @Override
-        public void reset() throws IOException {
-            content.reset();
-        }
-
-        @Override
-        public long skip(long n) throws IOException {
-            return content.skip(n);
-        }
-
-    }
-
-    private String endpoint;
-    private UUID agentId;
-
-    private Map<Category, Integer> categoryIds;
-    private Gson gson;
-    private DefaultHttpClient httpClient;
-    private String username;
-    private String password;
-    private SecureRandom random;
-
-    public WebStorage() {
-        categoryIds = new HashMap<>();
-        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
-        ClientConnectionManager connManager = new ThreadSafeClientConnManager();
-        DefaultHttpClient client = new DefaultHttpClient(connManager);
-        httpClient = client;
-        random = new SecureRandom();
-    }
-
-    private void initAuthentication(DefaultHttpClient client) throws MalformedURLException {
-        if (username != null && password != null) {
-            URL endpointURL = new URL(endpoint);
-            // TODO: Maybe also limit to realm like 'Thermostat Realm' or such?
-            AuthScope scope = new AuthScope(endpointURL.getHost(), endpointURL.getPort());
-            Credentials creds = new UsernamePasswordCredentials(username, password);
-            client.getCredentialsProvider().setCredentials(scope, creds);
-        }
-    }
-
-    private void ping() throws StorageException {
-        post(endpoint + "/ping", (HttpEntity) null).close();
-    }
-
-    private CloseableHttpEntity post(String url, List<NameValuePair> formparams) throws StorageException {
-        try {
-            return postImpl(url, formparams);
-        } catch (IOException ex) {
-            throw new StorageException(ex);
-        }
-    }
-
-    private CloseableHttpEntity postImpl(String url, List<NameValuePair> formparams) throws IOException {
-        HttpEntity entity;
-        if (formparams != null) {
-            entity = new UrlEncodedFormEntity(formparams, "UTF-8");
-        } else {
-            entity = null;
-        }
-        return postImpl(url, entity);
-    }
-
-    
-    private CloseableHttpEntity post(String url, HttpEntity entity) throws StorageException {
-        try {
-            return postImpl(url, entity);
-        } catch (IOException ex) {
-            throw new StorageException(ex);
-        }
-    }
-
-    private CloseableHttpEntity postImpl(String url, HttpEntity entity) throws IOException {
-        HttpPost httpPost = new HttpPost(url);
-        if (entity != null) {
-            httpPost.setEntity(entity);
-        }
-        HttpResponse response = httpClient.execute(httpPost);
-        StatusLine status = response.getStatusLine();
-        if (status.getStatusCode() != 200) {
-            throw new IOException("Server returned status: " + status);
-        }
-
-        return new CloseableHttpEntity(response.getEntity());
-    }
-
-    private static InputStream getContent(HttpEntity entity) {
-        try {
-            return entity.getContent();
-        } catch (IOException ex) {
-            throw new StorageException(ex);
-        }
-    }
-
-    private static Reader getContentAsReader(HttpEntity entity) {
-        InputStream in = getContent(entity);
-        return new InputStreamReader(in);
-    }
-
-    @Override
-    public void registerCategory(Category category) throws StorageException {
-        NameValuePair nameParam = new BasicNameValuePair("name", category.getName());
-        NameValuePair categoryParam = new BasicNameValuePair("category", gson.toJson(category));
-        List<NameValuePair> formparams = Arrays.asList(nameParam, categoryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/register-category", formparams)) {
-            Reader reader = getContentAsReader(entity);
-            Integer id = gson.fromJson(reader, Integer.class);
-            categoryIds.put(category, id);
-        }
-    }
-
-    @Override
-    public Query createQuery() {
-        return new WebQuery(categoryIds);
-    }
-
-    @Override
-    public Remove createRemove() {
-        return new WebRemove(categoryIds);
-    }
-
-    @Override
-    public WebUpdate createUpdate() {
-        return new WebUpdate(categoryIds);
-    }
-
-    @Override
-    public <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) throws StorageException {
-        ((WebQuery) query).setResultClassName(resultClass.getName());
-        NameValuePair queryParam = new BasicNameValuePair("query", gson.toJson(query));
-        List<NameValuePair> formparams = Arrays.asList(queryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/find-all", formparams)) {
-            Reader reader = getContentAsReader(entity);
-            T[] result = (T[]) gson.fromJson(reader, Array.newInstance(resultClass, 0).getClass());
-            return new WebCursor<T>(result);
-        }
-    }
-
-    @Override
-    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) throws StorageException {
-        ((WebQuery) query).setResultClassName(resultClass.getName());
-        NameValuePair queryParam = new BasicNameValuePair("query", gson.toJson(query));
-        List<NameValuePair> formparams = Arrays.asList(queryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/find-pojo", formparams)) {
-            Reader reader = getContentAsReader(entity);
-            T result = gson.fromJson(reader, resultClass);
-            return result;
-        }
-    }
-
-    @Override
-    public String getAgentId() {
-        return agentId.toString();
-    }
-
-    @Override
-    public Connection getConnection() {
-        return new WebConnection();
-    }
-
-    @Override
-    public long getCount(Category category) throws StorageException {
-        NameValuePair categoryParam = new BasicNameValuePair("category", gson.toJson(categoryIds.get(category)));
-        List<NameValuePair> formparams = Arrays.asList(categoryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/get-count", formparams)) {
-            Reader reader = getContentAsReader(entity);
-            long result = gson.fromJson(reader, Long.class);
-            return result;
-        }
-    }
-
-    @Override
-    public InputStream loadFile(String name) throws StorageException {
-        NameValuePair fileParam = new BasicNameValuePair("file", name);
-        List<NameValuePair> formparams = Arrays.asList(fileParam);
-        CloseableHttpEntity entity = post(endpoint + "/load-file", formparams);
-        return new WebDataStream(entity);
-    }
-
-    @Override
-    public void purge() throws StorageException {
-        post(endpoint + "/purge", (HttpEntity) null).close();
-    }
-
-    @Override
-    public void putPojo(Category category, boolean replace, AgentIdPojo pojo) throws StorageException {
-        // TODO: This logic should probably be moved elsewhere. I.e. out of the Storage API.
-        if (pojo.getAgentId() == null) {
-            pojo.setAgentId(getAgentId());
-        }
-
-        int categoryId = categoryIds.get(category);
-        WebInsert insert = new WebInsert(categoryId, replace, pojo.getClass().getName());
-        NameValuePair insertParam = new BasicNameValuePair("insert", gson.toJson(insert));
-        NameValuePair pojoParam = new BasicNameValuePair("pojo", gson.toJson(pojo));
-        List<NameValuePair> formparams = Arrays.asList(insertParam, pojoParam);
-        post(endpoint + "/put-pojo", formparams).close();
-    }
-
-    @Override
-    public void removePojo(Remove remove) throws StorageException {
-        NameValuePair removeParam = new BasicNameValuePair("remove", gson.toJson(remove));
-        List<NameValuePair> formparams = Arrays.asList(removeParam);
-        post(endpoint + "/remove-pojo", formparams).close();
-    }
-
-    @Override
-    public void saveFile(String name, InputStream in) throws StorageException {
-        InputStreamBody body = new InputStreamBody(in, name);
-        MultipartEntity mpEntity = new MultipartEntity();
-        mpEntity.addPart("file", body);
-        post(endpoint + "/save-file", mpEntity).close();
-    }
-
-    @Override
-    public void setAgentId(UUID agentId) {
-        this.agentId = agentId;
-    }
-
-    @Override
-    public void updatePojo(Update update) throws StorageException {
-        WebUpdate webUp = (WebUpdate) update;
-        List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
-        List<Object> values = new ArrayList<>(updateValues.size());
-        for (WebUpdate.UpdateValue updateValue : updateValues) {
-            values.add(updateValue.getValue());
-        }
-
-        NameValuePair updateParam = new BasicNameValuePair("update", gson.toJson(update));
-        NameValuePair valuesParam = new BasicNameValuePair("values", gson.toJson(values));
-        List<NameValuePair> formparams = Arrays.asList(updateParam, valuesParam);
-        post(endpoint + "/update-pojo", formparams).close();
-    }
-
-    public void setEndpoint(String endpoint) {
-        this.endpoint = endpoint;
-    }
-
-    public void setAuthConfig(String username, String password) {
-        this.username = username;
-        this.password = password;
-    }
-
-    @Override
-    public AuthToken generateToken() throws StorageException {
-        byte[] token = new byte[256];
-        random.nextBytes(token);
-        NameValuePair clientTokenParam = new BasicNameValuePair("client-token", Base64.encodeBase64String(token));
-        List<NameValuePair> formparams = Arrays.asList(clientTokenParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/generate-token", formparams)) {
-            byte[] authToken = EntityUtils.toByteArray(entity);
-            return new AuthToken(authToken, token);
-        } catch (IOException ex) {
-            throw new StorageException(ex);
-        }
-    }
-
-    @Override
-    public boolean verifyToken(AuthToken authToken) {
-        byte[] clientToken = authToken.getClientToken();
-        byte[] token = authToken.getToken();
-        NameValuePair clientTokenParam = new BasicNameValuePair("client-token", Base64.encodeBase64String(clientToken));
-        NameValuePair tokenParam = new BasicNameValuePair("token", Base64.encodeBase64String(token));
-        List<NameValuePair> formparams = Arrays.asList(clientTokenParam, tokenParam);
-        HttpResponse response = null;
-        try {
-            HttpEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
-            HttpPost httpPost = new HttpPost(endpoint + "/verify-token");
-            httpPost.setEntity(entity);
-            response = httpClient.execute(httpPost);
-            StatusLine status = response.getStatusLine();
-            return status.getStatusCode() == 200;
-        } catch (IOException ex) {
-            throw new StorageException(ex);
-        } finally {
-            if (response != null) {
-                try {
-                    EntityUtils.consume(response.getEntity());
-                } catch (IOException ex) {
-                    throw new StorageException(ex);
-                }
-            }
-        }
-    }
-
-}
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/WebStorageProvider.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-package com.redhat.thermostat.web.client;
-
-import com.redhat.thermostat.storage.config.AuthenticationConfiguration;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.StorageProvider;
-
-public class WebStorageProvider implements StorageProvider {
-
-    private StartupConfiguration config;
-    
-    @Override
-    public Storage createStorage() {
-        WebStorage storage = new WebStorage();
-        storage.setEndpoint(config.getDBConnectionString());
-        if (config instanceof AuthenticationConfiguration) {
-            AuthenticationConfiguration authConf = (AuthenticationConfiguration) config;
-            storage.setAuthConfig(authConf.getUsername(), authConf.getPassword());
-        }
-        return storage;
-    }
-
-    @Override
-    public void setConfig(StartupConfiguration config) {
-        this.config = config;
-    }
-
-    @Override
-    public boolean canHandleProtocol() {
-        // use http since this might be https at some point
-        return config.getDBConnectionString().startsWith("http");
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/Activator.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,61 @@
+/*
+ * 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.web.client.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.storage.core.StorageProvider;
+
+public class Activator implements BundleActivator {
+
+    private ServiceRegistration reg;
+    
+    @Override
+    public void start(BundleContext context) throws Exception {
+        WebStorageProvider storage = new WebStorageProvider();
+        this.reg = context.registerService(StorageProvider.class.getName(), storage, null);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        reg.unregister();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/CustomX509TrustManager.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,238 @@
+/*
+ * 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.web.client.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+/**
+ * Custom X509TrustManager which first attempts to verify peer certificates
+ * using Java's default trust manager. If that fails it uses thermostat's
+ * keystore file (if any) and uses it for another attempt to validate the
+ * server certificate. If that fails as well, the connection is not allowed.
+ * 
+ */
+class CustomX509TrustManager implements X509TrustManager {
+    
+    Logger logger = LoggingUtils.getLogger(CustomX509TrustManager.class);
+
+    /*
+     * The default X509TrustManager returned by SunX509. We'll delegate
+     * decisions to it, and fall back to the logic in this class if the default
+     * X509TrustManager doesn't trust it.
+     */
+    private X509TrustManager defaultX509TrustManager;
+    private X509TrustManager ourX509TrustManager;
+
+    // For testing
+    CustomX509TrustManager(X509TrustManager defaultTrustManager,
+            X509TrustManager ourTrustManager) {
+        this.defaultX509TrustManager = defaultTrustManager;
+        this.ourX509TrustManager = ourTrustManager;
+    }
+    
+    // For testing
+    CustomX509TrustManager(X509TrustManager defaultTrustManager,
+            File keyStoreFile, String keyStorePassword) {
+        this.defaultX509TrustManager = defaultTrustManager;
+        this.ourX509TrustManager = getOurTrustManager(keyStoreFile, keyStorePassword);
+    }
+
+    // For testing
+    CustomX509TrustManager(File keyStoreFile, String keyStorePassword) {
+        this.defaultX509TrustManager = getDefaultTrustManager();
+        this.ourX509TrustManager = getOurTrustManager(keyStoreFile, keyStorePassword);
+    }
+
+    /*
+     * Main constructor. Others are used for testing.
+     */
+    CustomX509TrustManager() {
+        this(SSLKeystoreConfiguration.getKeystoreFile(), SSLKeystoreConfiguration.getKeyStorePassword());
+    }
+ 
+    private X509TrustManager getDefaultTrustManager() {
+        try {
+            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+                    "SunX509", "SunJSSE");
+            // Passing a null-KeyStore in order to get default Java behaviour.
+            // See:
+            // http://docs.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
+            tmf.init((KeyStore) null);
+
+            TrustManager tms[] = tmf.getTrustManagers();
+
+            /*
+             * Iterate over the returned trustmanagers, look for an instance of
+             * X509TrustManager. If found, use that as our "default" trust
+             * manager.
+             */
+            for (int i = 0; i < tms.length; i++) {
+                if (tms[i] instanceof X509TrustManager) {
+                    logger.log(Level.FINE, "Using system trust manager.");
+                    return (X509TrustManager) tms[i];
+                }
+            }
+        } catch (NoSuchAlgorithmException | NoSuchProviderException | KeyStoreException e) {
+            logger.log(Level.WARNING, "Could not retrieve system trust manager", e);
+        }
+        return null;
+    }
+    
+    /*
+     * If thermostat trust store file exists and a X509TrustManager can be
+     * retrieved from said trust store it returns this X509TrustManager. Returns
+     * null if an exception is thrown along the way, the keystore file does not
+     * exist or no X509TrustManager was found in the backing trust store.
+     */
+    private X509TrustManager getOurTrustManager(File trustStoreFile,
+            String keyStorePassword) {
+        KeyStore trustStore = null;
+        if (trustStoreFile != null && trustStoreFile.exists()) {
+            try {
+                trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+                try (InputStream is = new FileInputStream(trustStoreFile)) {
+                    trustStore.load(is, keyStorePassword.toCharArray());
+                } catch (IOException | CertificateException
+                        | NoSuchAlgorithmException e) {
+                    logger.log(Level.WARNING,
+                            "Could not load Thermostat trust manager");
+                    return null;
+                }
+                TrustManagerFactory tmf = null;
+                tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
+                tmf.init(trustStore);
+
+                for (TrustManager tm : tmf.getTrustManagers()) {
+                    if (tm instanceof X509TrustManager) {
+                        logger.log(Level.FINE,
+                                "Using Thermostat trust manager.");
+                        return (X509TrustManager) tm;
+                    }
+                }
+            } catch (NoSuchAlgorithmException | NoSuchProviderException
+                    | KeyStoreException e) {
+                logger.log(Level.WARNING,
+                        "Could not load Thermostat trust manager");
+                return null;
+            }
+        }
+        logger.log(Level.FINE, "No Thermostat trust manager found");
+        return null;
+    }
+
+    @Override
+    public void checkClientTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+        // no-op: we don't support client authentication
+        logger.log(Level.INFO, "Checking client authentication: Allowing all client certificates!");
+    }
+
+    @Override
+    public void checkServerTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+        logger.log(Level.FINE, "Checking server certificate");
+        if (defaultX509TrustManager != null) {
+            try {
+                defaultX509TrustManager.checkServerTrusted(chain, authType);
+                logger.log(Level.FINE, "Server certificate check passed using system trust manager");
+                return;
+            } catch (CertificateException e) {
+                if (ourX509TrustManager != null) {
+                    // try our trust manager instead
+                    ourX509TrustManager.checkServerTrusted(chain, authType);
+                    logger.log(Level.FINE, "Server certificate check passed using Thermostat trust manager");
+                    return;
+                } else {
+                    // just rethrow
+                    logger.log(Level.WARNING, "Server certificate check FAILED!", e);
+                    throw e;
+                }
+            }
+        } else if (ourX509TrustManager != null) {
+            ourX509TrustManager.checkServerTrusted(chain, authType);
+            logger.log(Level.FINE, "Server certificate check passed using Thermostat trust manager");
+            return;
+        }
+        logger.log(Level.SEVERE, "Server certificate could not be checked. No trust managers found. Stopping now.");
+        // Default to not trusting this cert
+        throw new CertificateException("Certificate verification failed!");
+    }
+
+    /*
+     * Union of CA's trusted by default trust manager and the thermostat trust
+     * manager (if any).
+     * 
+     * (non-Javadoc)
+     * 
+     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
+     */
+    @Override
+    public X509Certificate[] getAcceptedIssuers() {
+        Set<X509Certificate> trustedSet = new HashSet<>();
+        if (defaultX509TrustManager != null) {
+            trustedSet.addAll(Arrays.asList(defaultX509TrustManager
+                    .getAcceptedIssuers()));
+        }
+        if (ourX509TrustManager != null) {
+            trustedSet.addAll(Arrays.asList(ourX509TrustManager
+                    .getAcceptedIssuers()));
+        }
+        X509Certificate[] certs = trustedSet.toArray(new X509Certificate[0]);
+        return certs;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/SSLKeystoreConfiguration.java	Mon Dec 10 15:05:37 2012 +0100
@@ -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.web.client.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.common.config.ConfigUtils;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+
+public class SSLKeystoreConfiguration {
+
+    private static Properties clientProps = null;
+    private static final String KEYSTORE_FILE_KEY = "KEYSTORE_FILE";
+    private static final String KEYSTORE_FILE_PWD_KEY = "KEYSTORE_PASSWORD";
+
+    /**
+     * 
+     * @return The keystore file as specified in
+     *         $THERMOSTAT_HOME/client.properties if any. null otherwise.
+     */
+    public static File getKeystoreFile() {
+        try {
+            loadClientProperties();
+        } catch (InvalidConfigurationException e) {
+            // Thermostat home not set? Should have failed earlier. Do something
+            // reasonable.
+            return null;
+        }
+        String path = clientProps.getProperty(KEYSTORE_FILE_KEY);
+        if (path != null) {
+            File file = new File(path);
+            return file;
+        }
+        return null;
+    }
+
+    /**
+     * 
+     * @return The keystore file as specified in
+     *         $THERMOSTAT_HOME/client.properties if any. The empty string
+     *         otherwise.
+     * @throws InvalidConfigurationException
+     */
+    public static String getKeyStorePassword() {
+        try {
+            loadClientProperties();
+        } catch (InvalidConfigurationException e) {
+            // Thermostat home not set? Do something reasonable
+            return "";
+        }
+        String pwd = clientProps.getProperty(KEYSTORE_FILE_PWD_KEY);
+        if (pwd == null) {
+            return "";
+        } else {
+            return pwd;
+        }
+    }
+
+    // testing hook
+    static void initClientProperties(File clientPropertiesFile) {
+        clientProps = new Properties();
+        try {
+            clientProps.load(new FileInputStream(clientPropertiesFile));
+        } catch (IOException | IllegalArgumentException e) {
+            // Could not load client properties file. This is fine as it's
+            // an optional config after all.
+        }
+    }
+
+    private static void loadClientProperties()
+            throws InvalidConfigurationException {
+        if (clientProps == null) {
+            File thermostatEtcDir = new File(ConfigUtils.getThermostatHome(),
+                    "etc");
+            File clientPropertiesFile = new File(thermostatEtcDir,
+                    "client.properties");
+            initClientProperties(clientPropertiesFile);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebCursor.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,65 @@
+/*
+ * 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.web.client.internal;
+
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.model.Pojo;
+
+class WebCursor<T extends Pojo> implements Cursor<T> {
+
+    private T[] data;
+    private int index;
+
+    WebCursor(T[] data) {
+        this.data = data;
+        index = 0;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return index < data.length;
+    }
+
+    @Override
+    public T next() {
+        T result = data[index];
+        index++;
+        return result;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,604 @@
+/*
+ * 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.web.client.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.lang.reflect.Array;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.StatusLine;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.storage.core.AuthToken;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Connection;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.SecureStorage;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.StorageException;
+import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.model.AgentIdPojo;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.web.common.ThermostatGSONConverter;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+public class WebStorage implements Storage, SecureStorage {
+
+    private static final String HTTPS_PREFIX = "https";
+    final Logger logger = LoggingUtils.getLogger(WebStorage.class);
+
+    private static class CloseableHttpEntity implements Closeable, HttpEntity {
+
+        private HttpEntity entity;
+
+        CloseableHttpEntity(HttpEntity entity) {
+            this.entity = entity;
+        }
+
+        @Override
+        public void consumeContent() throws IOException {
+            EntityUtils.consume(entity);
+        }
+
+        @Override
+        public InputStream getContent() throws IOException,
+                IllegalStateException {
+            return entity.getContent();
+        }
+
+        @Override
+        public Header getContentEncoding() {
+            return entity.getContentEncoding();
+        }
+
+        @Override
+        public long getContentLength() {
+            return entity.getContentLength();
+        }
+
+        @Override
+        public Header getContentType() {
+            return entity.getContentType();
+        }
+
+        @Override
+        public boolean isChunked() {
+            return entity.isChunked();
+        }
+
+        @Override
+        public boolean isRepeatable() {
+            return entity.isRepeatable();
+        }
+
+        @Override
+        public boolean isStreaming() {
+            return entity.isStreaming();
+        }
+
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            entity.writeTo(out);
+        }
+
+        @Override
+        public void close() {
+            try {
+                EntityUtils.consume(entity);
+            } catch (IOException ex) {
+                throw new StorageException(ex);
+            }
+        }
+
+    }
+
+    private final class WebConnection extends Connection {
+        WebConnection() {
+            connected = true;
+        }
+
+        @Override
+        public void disconnect() {
+            connected = false;
+            fireChanged(ConnectionStatus.DISCONNECTED);
+        }
+
+        @Override
+        public void connect() {
+            try {
+                initAuthentication(httpClient);
+                ping();
+                connected = true;
+                logger.fine("Connected to storage");
+                fireChanged(ConnectionStatus.CONNECTED);
+            } catch (Exception ex) {
+                logger.log(Level.WARNING, "Could not connect to storage!", ex);
+                fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
+            }
+        }
+
+        @Override
+        public void setUrl(String url) {
+            super.setUrl(url);
+            endpoint = url;
+        }
+
+        @Override
+        public String getUrl() {
+            return endpoint;
+        }
+    }
+
+    private static class WebDataStream extends InputStream {
+
+        private CloseableHttpEntity entity;
+        private InputStream content;
+
+        WebDataStream(CloseableHttpEntity entity) {
+            this.entity = entity;
+            try {
+                content = entity.getContent();
+            } catch (IllegalStateException | IOException e) {
+                throw new StorageException(e);
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            content.close();
+            entity.close();
+        }
+
+        @Override
+        public int read() throws IOException {
+            return content.read();
+        }
+
+        @Override
+        public int available() throws IOException {
+            return content.available();
+        }
+
+        @Override
+        public void mark(int readlimit) {
+            content.mark(readlimit);
+        }
+
+        @Override
+        public boolean markSupported() {
+            return content.markSupported();
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            return content.read(b);
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            return content.read(b, off, len);
+        }
+
+        @Override
+        public void reset() throws IOException {
+            content.reset();
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            return content.skip(n);
+        }
+
+    }
+
+    private String endpoint;
+    private UUID agentId;
+
+    private Map<Category, Integer> categoryIds;
+    private Gson gson;
+    // package private for testing
+    DefaultHttpClient httpClient;
+    private String username;
+    private String password;
+    private SecureRandom random;
+
+    public WebStorage(StartupConfiguration config) throws StorageException {
+        categoryIds = new HashMap<>();
+        gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class,
+                new ThermostatGSONConverter()).create();
+        ClientConnectionManager connManager = new ThreadSafeClientConnManager();
+        DefaultHttpClient client = new DefaultHttpClient(connManager);
+        httpClient = client;
+        random = new SecureRandom();
+
+        // setup SSL if necessary
+        if (config.getDBConnectionString().startsWith(HTTPS_PREFIX)) {
+            registerSSLScheme(connManager);
+        }
+    }
+
+    private void registerSSLScheme(ClientConnectionManager conManager)
+            throws StorageException {
+        try {
+            SSLContext sc = SSLContext.getInstance("TLS");
+            X509TrustManager ourTm;
+            try {
+                ourTm = new CustomX509TrustManager();
+            } catch (Exception e) {
+                throw new StorageException(e);
+            }
+            TrustManager[] tms = new TrustManager[] { ourTm };
+            sc.init(null, tms, new SecureRandom());
+            SSLSocketFactory socketFactory = new SSLSocketFactory(sc);
+            Scheme sch = new Scheme("https", 443, socketFactory);
+            conManager.getSchemeRegistry().register(sch);
+        } catch ( GeneralSecurityException e) {
+            throw new StorageException(e);
+        }
+    }
+
+    private void initAuthentication(DefaultHttpClient client)
+            throws MalformedURLException {
+        if (username != null && password != null) {
+            URL endpointURL = new URL(endpoint);
+            // TODO: Maybe also limit to realm like 'Thermostat Realm' or such?
+            AuthScope scope = new AuthScope(endpointURL.getHost(),
+                    endpointURL.getPort());
+            Credentials creds = new UsernamePasswordCredentials(username,
+                    password);
+            client.getCredentialsProvider().setCredentials(scope, creds);
+        }
+    }
+
+    private void ping() throws StorageException {
+        post(endpoint + "/ping", (HttpEntity) null).close();
+    }
+
+    private CloseableHttpEntity post(String url, List<NameValuePair> formparams)
+            throws StorageException {
+        try {
+            return postImpl(url, formparams);
+        } catch (IOException ex) {
+            throw new StorageException(ex);
+        }
+    }
+
+    private CloseableHttpEntity postImpl(String url,
+            List<NameValuePair> formparams) throws IOException {
+        HttpEntity entity;
+        if (formparams != null) {
+            entity = new UrlEncodedFormEntity(formparams, "UTF-8");
+        } else {
+            entity = null;
+        }
+        return postImpl(url, entity);
+    }
+
+    private CloseableHttpEntity post(String url, HttpEntity entity)
+            throws StorageException {
+        try {
+            return postImpl(url, entity);
+        } catch (IOException ex) {
+            throw new StorageException(ex);
+        }
+    }
+
+    private CloseableHttpEntity postImpl(String url, HttpEntity entity)
+            throws IOException {
+        HttpPost httpPost = new HttpPost(url);
+        if (entity != null) {
+            httpPost.setEntity(entity);
+        }
+        HttpResponse response = httpClient.execute(httpPost);
+        StatusLine status = response.getStatusLine();
+        if (status.getStatusCode() != 200) {
+            throw new IOException("Server returned status: " + status);
+        }
+
+        return new CloseableHttpEntity(response.getEntity());
+    }
+
+    private static InputStream getContent(HttpEntity entity) {
+        try {
+            return entity.getContent();
+        } catch (IOException ex) {
+            throw new StorageException(ex);
+        }
+    }
+
+    private static Reader getContentAsReader(HttpEntity entity) {
+        InputStream in = getContent(entity);
+        return new InputStreamReader(in);
+    }
+
+    @Override
+    public void registerCategory(Category category) throws StorageException {
+        NameValuePair nameParam = new BasicNameValuePair("name",
+                category.getName());
+        NameValuePair categoryParam = new BasicNameValuePair("category",
+                gson.toJson(category));
+        List<NameValuePair> formparams = Arrays
+                .asList(nameParam, categoryParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/register-category",
+                formparams)) {
+            Reader reader = getContentAsReader(entity);
+            Integer id = gson.fromJson(reader, Integer.class);
+            categoryIds.put(category, id);
+        }
+    }
+
+    @Override
+    public Query createQuery() {
+        return new WebQuery(categoryIds);
+    }
+
+    @Override
+    public Remove createRemove() {
+        return new WebRemove(categoryIds);
+    }
+
+    @Override
+    public WebUpdate createUpdate() {
+        return new WebUpdate(categoryIds);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T extends Pojo> Cursor<T> findAllPojos(Query query,
+            Class<T> resultClass) throws StorageException {
+        ((WebQuery) query).setResultClassName(resultClass.getName());
+        NameValuePair queryParam = new BasicNameValuePair("query",
+                gson.toJson(query));
+        List<NameValuePair> formparams = Arrays.asList(queryParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/find-all",
+                formparams)) {
+            Reader reader = getContentAsReader(entity);
+            T[] result = (T[]) gson.fromJson(reader,
+                    Array.newInstance(resultClass, 0).getClass());
+            return new WebCursor<T>(result);
+        }
+    }
+
+    @Override
+    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass)
+            throws StorageException {
+        ((WebQuery) query).setResultClassName(resultClass.getName());
+        NameValuePair queryParam = new BasicNameValuePair("query",
+                gson.toJson(query));
+        List<NameValuePair> formparams = Arrays.asList(queryParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/find-pojo",
+                formparams)) {
+            Reader reader = getContentAsReader(entity);
+            T result = gson.fromJson(reader, resultClass);
+            return result;
+        }
+    }
+
+    @Override
+    public String getAgentId() {
+        return agentId.toString();
+    }
+
+    @Override
+    public Connection getConnection() {
+        return new WebConnection();
+    }
+
+    @Override
+    public long getCount(Category category) throws StorageException {
+        NameValuePair categoryParam = new BasicNameValuePair("category",
+                gson.toJson(categoryIds.get(category)));
+        List<NameValuePair> formparams = Arrays.asList(categoryParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/get-count",
+                formparams)) {
+            Reader reader = getContentAsReader(entity);
+            long result = gson.fromJson(reader, Long.class);
+            return result;
+        }
+    }
+
+    @Override
+    public InputStream loadFile(String name) throws StorageException {
+        NameValuePair fileParam = new BasicNameValuePair("file", name);
+        List<NameValuePair> formparams = Arrays.asList(fileParam);
+        CloseableHttpEntity entity = post(endpoint + "/load-file", formparams);
+        return new WebDataStream(entity);
+    }
+
+    @Override
+    public void purge() throws StorageException {
+        post(endpoint + "/purge", (HttpEntity) null).close();
+    }
+
+    @Override
+    public void putPojo(Category category, boolean replace, AgentIdPojo pojo)
+            throws StorageException {
+        // TODO: This logic should probably be moved elsewhere. I.e. out of the
+        // Storage API.
+        if (pojo.getAgentId() == null) {
+            pojo.setAgentId(getAgentId());
+        }
+
+        int categoryId = categoryIds.get(category);
+        WebInsert insert = new WebInsert(categoryId, replace, pojo.getClass()
+                .getName());
+        NameValuePair insertParam = new BasicNameValuePair("insert",
+                gson.toJson(insert));
+        NameValuePair pojoParam = new BasicNameValuePair("pojo",
+                gson.toJson(pojo));
+        List<NameValuePair> formparams = Arrays.asList(insertParam, pojoParam);
+        post(endpoint + "/put-pojo", formparams).close();
+    }
+
+    @Override
+    public void removePojo(Remove remove) throws StorageException {
+        NameValuePair removeParam = new BasicNameValuePair("remove",
+                gson.toJson(remove));
+        List<NameValuePair> formparams = Arrays.asList(removeParam);
+        post(endpoint + "/remove-pojo", formparams).close();
+    }
+
+    @Override
+    public void saveFile(String name, InputStream in) throws StorageException {
+        InputStreamBody body = new InputStreamBody(in, name);
+        MultipartEntity mpEntity = new MultipartEntity();
+        mpEntity.addPart("file", body);
+        post(endpoint + "/save-file", mpEntity).close();
+    }
+
+    @Override
+    public void setAgentId(UUID agentId) {
+        this.agentId = agentId;
+    }
+
+    @Override
+    public void updatePojo(Update update) throws StorageException {
+        WebUpdate webUp = (WebUpdate) update;
+        List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
+        List<Object> values = new ArrayList<>(updateValues.size());
+        for (WebUpdate.UpdateValue updateValue : updateValues) {
+            values.add(updateValue.getValue());
+        }
+
+        NameValuePair updateParam = new BasicNameValuePair("update",
+                gson.toJson(update));
+        NameValuePair valuesParam = new BasicNameValuePair("values",
+                gson.toJson(values));
+        List<NameValuePair> formparams = Arrays
+                .asList(updateParam, valuesParam);
+        post(endpoint + "/update-pojo", formparams).close();
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public void setAuthConfig(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public AuthToken generateToken() throws StorageException {
+        byte[] token = new byte[256];
+        random.nextBytes(token);
+        NameValuePair clientTokenParam = new BasicNameValuePair("client-token", Base64.encodeBase64String(token));
+        List<NameValuePair> formparams = Arrays.asList(clientTokenParam);
+        try (CloseableHttpEntity entity = post(endpoint + "/generate-token", formparams)) {
+            byte[] authToken = EntityUtils.toByteArray(entity);
+            return new AuthToken(authToken, token);
+        } catch (IOException ex) {
+            throw new StorageException(ex);
+        }
+    }
+
+    @Override
+    public boolean verifyToken(AuthToken authToken) {
+        byte[] clientToken = authToken.getClientToken();
+        byte[] token = authToken.getToken();
+        NameValuePair clientTokenParam = new BasicNameValuePair("client-token", Base64.encodeBase64String(clientToken));
+        NameValuePair tokenParam = new BasicNameValuePair("token", Base64.encodeBase64String(token));
+        List<NameValuePair> formparams = Arrays.asList(clientTokenParam, tokenParam);
+        HttpResponse response = null;
+        try {
+            HttpEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
+            HttpPost httpPost = new HttpPost(endpoint + "/verify-token");
+            httpPost.setEntity(entity);
+            response = httpClient.execute(httpPost);
+            StatusLine status = response.getStatusLine();
+            return status.getStatusCode() == 200;
+        } catch (IOException ex) {
+            throw new StorageException(ex);
+        } finally {
+            if (response != null) {
+                try {
+                    EntityUtils.consume(response.getEntity());
+                } catch (IOException ex) {
+                    throw new StorageException(ex);
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorageProvider.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,34 @@
+package com.redhat.thermostat.web.client.internal;
+
+import com.redhat.thermostat.storage.config.AuthenticationConfiguration;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.StorageProvider;
+
+public class WebStorageProvider implements StorageProvider {
+
+    private StartupConfiguration config;
+    
+    @Override
+    public Storage createStorage() {
+        WebStorage storage = new WebStorage(config);
+        storage.setEndpoint(config.getDBConnectionString());
+        if (config instanceof AuthenticationConfiguration) {
+            AuthenticationConfiguration authConf = (AuthenticationConfiguration) config;
+            storage.setAuthConfig(authConf.getUsername(), authConf.getPassword());
+        }
+        return storage;
+    }
+
+    @Override
+    public void setConfig(StartupConfiguration config) {
+        this.config = config;
+    }
+
+    @Override
+    public boolean canHandleProtocol() {
+        // use http since this might be https at some point
+        return config.getDBConnectionString().startsWith("http");
+    }
+
+}
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/TestObj.java	Mon Dec 10 14:30:36 2012 +0100
+++ /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.web.client;
-
-import com.redhat.thermostat.storage.core.Entity;
-import com.redhat.thermostat.storage.core.Persist;
-import com.redhat.thermostat.storage.model.BasePojo;
-
-@Entity
-public class TestObj extends BasePojo {
-
-    
-    private String property1;
-
-    @Persist
-    public void setProperty1(String property1) {
-        this.property1 = property1;
-    }
-
-    @Persist
-    public String getProperty1() {
-        return property1;
-    }
-
-    public boolean equals(Object o) {
-        if (! (o instanceof TestObj)) {
-            return false;
-        }
-        TestObj other = (TestObj) o;
-        return property1.equals(other.property1);
-    }
-}
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/WebStorageTest.java	Mon Dec 10 14:30:36 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,543 +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.web.client;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.codec.binary.Base64;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
-import com.redhat.thermostat.storage.core.AuthToken;
-import com.redhat.thermostat.storage.core.AuthToken;
-import com.redhat.thermostat.storage.core.Categories;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.test.FreePortFinder;
-import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.web.common.Qualifier;
-import com.redhat.thermostat.web.common.WebQuery;
-import com.redhat.thermostat.web.common.WebInsert;
-import com.redhat.thermostat.web.common.WebRemove;
-import com.redhat.thermostat.web.common.WebUpdate;
-
-public class WebStorageTest {
-
-    private Server server;
-
-    private int port;
-
-    private String requestBody;
-
-    private String responseBody;
-    private Map<String,String> headers;
-    private String method;
-    private String requestURI;
-    private int responseStatus;
-
-    private static Category category;
-    private static Key<String> key1;
-    private static Key<Integer> key2;
-
-    private WebStorage storage;
-
-    @BeforeClass
-    public static void setupCategory() {
-        key1 = new Key<>("property1", true);
-        key2 = new Key<>("property2", true);
-        category = new Category("test", key1);
-    }
-
-    @AfterClass
-    public static void cleanupCategory() {
-        Categories.remove(category);
-        category = null;
-        key1 = null;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        
-        port = FreePortFinder.findFreePort(new TryPort() {
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-
-        storage = new WebStorage();
-        storage.setEndpoint("http://localhost:" + port + "/");
-        storage.setAgentId(new UUID(123, 456));
-        headers = new HashMap<>();
-        requestURI = null;
-        method = null;
-        responseStatus = HttpServletResponse.SC_OK;
-        registerCategory();
-    }
-
-    private void startServer(int port) throws Exception {
-        server = new Server(port);
-        server.setHandler(new AbstractHandler() {
-            
-            @Override
-            public void handle(String target, Request baseRequest,
-                    HttpServletRequest request, HttpServletResponse response)
-                    throws IOException, ServletException {
-                Enumeration<String> headerNames = request.getHeaderNames();
-                while (headerNames.hasMoreElements()) {
-                    String headerName = headerNames.nextElement();
-                    headers.put(headerName, request.getHeader(headerName));
-                }
-
-                method = request.getMethod();
-                requestURI = request.getRequestURI();
-
-                // Read request body.
-                StringBuilder body = new StringBuilder();
-                Reader reader = request.getReader();
-                while (true) {
-                    int read = reader.read();
-                    if (read == -1) {
-                        break;
-                    }
-                    body.append((char) read);
-                }
-                requestBody = body.toString();
-                // Send response body.
-                response.setStatus(responseStatus);
-                if (responseBody != null) {
-                    response.getWriter().write(responseBody);
-                }
-                baseRequest.setHandled(true);
-            }
-        });
-        server.start();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-
-        headers = null;
-        requestURI = null;
-        method = null;
-        storage = null;
-
-        server.stop();
-        server.join();
-    }
-
-    private void registerCategory() {
-
-        // Return 42 for categoryId.
-        Gson gson = new Gson();
-        responseBody = gson.toJson(42);
-
-        storage.registerCategory(category);
-    }
-
-    @Test
-    public void testFindPojo() throws UnsupportedEncodingException, IOException {
-
-        TestObj obj = new TestObj();
-        obj.setProperty1("fluffor");
-        Gson gson = new Gson();
-        responseBody = gson.toJson(obj);
-
-        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
-
-        TestObj result = storage.findPojo(query, TestObj.class);
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("query", parts[0]);
-        WebQuery restQuery = gson.fromJson(parts[1], WebQuery.class);
-
-        assertEquals(42, restQuery.getCategoryId());
-        List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qual = qualifiers.get(0);
-        assertEquals(new Key<String>("property1", true), qual.getKey());
-        assertEquals(Criteria.EQUALS, qual.getCriteria());
-        assertEquals("fluff", qual.getValue());
-
-        assertEquals("fluffor", result.getProperty1());
-    }
-
-    @Test
-    public void testFindAllPojos() throws UnsupportedEncodingException, IOException {
-
-        TestObj obj1 = new TestObj();
-        obj1.setProperty1("fluffor1");
-        TestObj obj2 = new TestObj();
-        obj2.setProperty1("fluffor2");
-        Gson gson = new Gson();
-        responseBody = gson.toJson(Arrays.asList(obj1, obj2));
-
-        Key<String> key1 = new Key<>("property1", true);
-        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
-
-        Cursor<TestObj> results = storage.findAllPojos(query, TestObj.class);
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("query", parts[0]);
-        WebQuery restQuery = gson.fromJson(parts[1], WebQuery.class);
-
-        assertEquals(42, restQuery.getCategoryId());
-        List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qual = qualifiers.get(0);
-        assertEquals(new Key<String>("property1", true), qual.getKey());
-        assertEquals(Criteria.EQUALS, qual.getCriteria());
-        assertEquals("fluff", qual.getValue());
-
-        assertTrue(results.hasNext());
-        assertEquals("fluffor1", results.next().getProperty1());
-        assertTrue(results.hasNext());
-        assertEquals("fluffor2", results.next().getProperty1());
-        assertFalse(results.hasNext());
-    }
-
-    @Test
-    public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
-
-        TestObj obj = new TestObj();
-        obj.setProperty1("fluff");
-
-        storage.putPojo(category, true, obj);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("insert", parts[0]);
-        WebInsert insert = gson.fromJson(parts[1], WebInsert.class);
-        assertEquals(42, insert.getCategoryId());
-        assertEquals(true, insert.isReplace());
-        assertEquals(TestObj.class.getName(), insert.getPojoClass());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("pojo", parts[0]);
-        Object resultObj = gson.fromJson(parts[1], Class.forName(insert.getPojoClass()));
-        assertEquals(obj, resultObj);
-    }
-
-    @Test
-    public void testCreateRemove() {
-        WebRemove remove = (WebRemove) storage.createRemove();
-        assertNotNull(remove);
-        remove = remove.from(category);
-        assertEquals(42, remove.getCategoryId());
-        assertNotNull(remove);
-        remove = remove.where(key1, "test");
-        assertNotNull(remove);
-        List<Qualifier<?>> qualifiers = remove.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
-    }
-
-    @Test
-    public void testRemovePojo() throws UnsupportedEncodingException, IOException {
-        Remove remove = storage.createRemove().from(category).where(key1, "test");
-        storage.removePojo(remove);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("remove", parts[0]);
-        WebRemove actualRemove = gson.fromJson(parts[1], WebRemove.class);
-        
-        assertEquals(42, actualRemove.getCategoryId());
-        List<Qualifier<?>> qualifiers = actualRemove.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
-    }
-
-    @Test
-    public void testCreateUpdate() {
-        WebUpdate update = (WebUpdate) storage.createUpdate();
-        assertNotNull(update);
-        update = update.from(category);
-        assertEquals(42, update.getCategoryId());
-        assertNotNull(update);
-        update = update.where(key1, "test");
-        assertNotNull(update);
-        List<Qualifier<?>> qualifiers = update.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
-        update = update.set(key1, "fluff");
-        assertNotNull(update);
-        List<WebUpdate.UpdateValue> updates = update.getUpdates();
-        assertEquals(1, updates.size());
-        assertEquals("fluff", updates.get(0).getValue());
-        assertEquals(key1, updates.get(0).getKey());
-        assertEquals("java.lang.String", updates.get(0).getValueClass());
-    }
-
-    @Test
-    public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
-
-        WebUpdate update = storage.createUpdate().from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
-        storage.updatePojo(update);
-
-        Gson gson = new Gson();
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String [] params = line.split("&");
-        assertEquals(2, params.length);
-        String[] parts = params[0].split("=");
-        assertEquals("update", parts[0]);
-        WebUpdate receivedUpdate = gson.fromJson(parts[1], WebUpdate.class);
-        assertEquals(42, receivedUpdate.getCategoryId());
-
-        List<WebUpdate.UpdateValue> updates = receivedUpdate.getUpdates();
-        assertEquals(2, updates.size());
-
-        WebUpdate.UpdateValue update1 = updates.get(0);
-        assertEquals(key1, update1.getKey());
-        assertEquals("java.lang.String", update1.getValueClass());
-        assertNull(update1.getValue());
-
-        WebUpdate.UpdateValue update2 = updates.get(1);
-        assertEquals(key2, update2.getKey());
-        assertEquals("java.lang.Integer", update2.getValueClass());
-        assertNull(update2.getValue());
-
-        List<Qualifier<?>> qualifiers = receivedUpdate.getQualifiers();
-        assertEquals(1, qualifiers.size());
-        Qualifier<?> qualifier = qualifiers.get(0);
-        assertEquals(key1, qualifier.getKey());
-        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
-        assertEquals("test", qualifier.getValue());
-
-        parts = params[1].split("=");
-        assertEquals(2, parts.length);
-        assertEquals("values", parts[0]);
-        JsonParser jsonParser = new JsonParser();
-        JsonArray jsonArray = jsonParser.parse(parts[1]).getAsJsonArray();
-        String value1 = gson.fromJson(jsonArray.get(0), String.class);
-        assertEquals("fluff", value1);
-        int value2 = gson.fromJson(jsonArray.get(1), Integer.class);
-        assertEquals(42, value2);
-    }
-
-    @Test
-    public void testGetCount() throws UnsupportedEncodingException, IOException {
-
-        Gson gson = new Gson();
-        responseBody = gson.toJson(12345);
-
-        long result = storage.getCount(category);
-
-        StringReader reader = new StringReader(requestBody);
-        BufferedReader bufRead = new BufferedReader(reader);
-        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
-        String[] parts = line.split("=");
-        assertEquals("category", parts[0]);
-        assertEquals("42", parts[1]);
-        assertEquals(12345, result);
-    }
-
-    @Test
-    public void testSaveFile() {
-        String data = "Hello World";
-        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
-        storage.saveFile("fluff", in);
-        assertEquals("chunked", headers.get("Transfer-Encoding"));
-        String contentType = headers.get("Content-Type");
-        assertTrue(contentType.startsWith("multipart/form-data; boundary="));
-        String boundary = contentType.split("boundary=")[1];
-        String[] lines = requestBody.split("\n");
-        assertEquals("--" + boundary, lines[0].trim());
-        assertEquals("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"", lines[1].trim());
-        assertEquals("Content-Type: application/octet-stream", lines[2].trim());
-        assertEquals("Content-Transfer-Encoding: binary", lines[3].trim());
-        assertEquals("", lines[4].trim());
-        assertEquals("Hello World", lines[5].trim());
-        assertEquals("--" + boundary + "--", lines[6].trim());
-        
-    }
-
-    @Test
-    public void testLoadFile() throws IOException {
-        responseBody = "Hello World";
-        InputStream in = storage.loadFile("fluff");
-        assertEquals("file=fluff", requestBody.trim());
-        byte[] data = new byte[11];
-        int totalRead = 0;
-        while (totalRead < 11) {
-            int read = in.read(data, totalRead, 11 - totalRead);
-            if (read < 0) {
-                fail();
-            }
-            totalRead += read;
-        }
-        assertEquals("Hello World", new String(data));
-
-    }
-
-    @Test
-    public void testPurge() {
-        storage.purge();
-        assertEquals("POST", method);
-        assertTrue(requestURI.endsWith("/purge"));
-    }
-
-    @Test
-    public void testGenerateToken() throws UnsupportedEncodingException {
-        responseBody = "flufftoken";
-
-        AuthToken authToken = storage.generateToken();
-
-        assertTrue(requestURI.endsWith("/generate-token"));
-        assertEquals("POST", method);
-
-        String[] requestParts = requestBody.split("=");
-        assertEquals("client-token", requestParts[0]);
-        String clientTokenParam = URLDecoder.decode(requestParts[1], "UTF-8");
-        byte[] clientToken = Base64.decodeBase64(clientTokenParam);
-        assertEquals(256, clientToken.length);
-
-        assertTrue(authToken instanceof AuthToken);
-        AuthToken token = (AuthToken) authToken;
-        byte[] tokenBytes = token.getToken();
-        assertEquals("flufftoken", new String(tokenBytes));
-
-        assertTrue(Arrays.equals(clientToken, token.getClientToken()));
-
-        // Send another request and verify that we send a different client-token every time.
-        storage.generateToken();
-
-        requestParts = requestBody.split("=");
-        assertEquals("client-token", requestParts[0]);
-        clientTokenParam = URLDecoder.decode(requestParts[1], "UTF-8");
-        byte[] clientToken2 = Base64.decodeBase64(clientTokenParam);
-        assertFalse(Arrays.equals(clientToken, clientToken2));
-
-    }
-
-    @Test
-    public void testVerifyToken() throws UnsupportedEncodingException {
-        byte[] token = "stuff".getBytes();
-        byte[] clientToken = "fluff".getBytes();
-        AuthToken authToken = new AuthToken(token, clientToken);
-
-        responseStatus = 200;
-        boolean ok = storage.verifyToken(authToken);
-        assertTrue(requestURI.endsWith("/verify-token"));
-        assertEquals("POST", method);
-        String[] requestParts = requestBody.split("&");
-        assertEquals(2, requestParts.length);
-        String[] clientTokenParts = requestParts[0].split("=");
-        assertEquals(2, clientTokenParts.length);
-        assertEquals("client-token", clientTokenParts[0]);
-        String urlDecoded = URLDecoder.decode(clientTokenParts[1], "UTF-8");
-        String base64decoded = new String(Base64.decodeBase64(urlDecoded));
-        assertEquals("fluff", base64decoded);
-        String[] authTokenParts = requestParts[1].split("=");
-        assertEquals(2, authTokenParts.length);
-        assertEquals("token", authTokenParts[0]);
-        urlDecoded = URLDecoder.decode(authTokenParts[1], "UTF-8");
-        base64decoded = new String(Base64.decodeBase64(urlDecoded));
-        assertEquals("stuff", base64decoded);
-        assertTrue(ok);
-
-        // Try another one in which verification fails.
-        responseStatus = 401;
-        ok = storage.verifyToken(authToken);
-        assertFalse(ok);
-        
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/CustomX509TrustManagerTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,140 @@
+/*
+ * 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.web.client.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.web.client.internal.CustomX509TrustManager;
+
+/**
+ * This trust manager test uses files in src/test/resources. Files are as
+ * follows:
+ * 
+ * empty.keystore => emtpy file (not a real keystore file)
+ * 
+ * ca.crt => a openssl generated X509 certificate (representing a custom CA)
+ * 
+ * test_ca.keystore => a Java keystore file with ca.crt imported. Uses keystore
+ * password "testpassword". Used command sequence to generate this file:
+ * 
+ * $ keytool -genkey -alias com.redhat -keyalg RSA -keystore test_ca.keystore -keysize 2048
+ * $ keytool -import -trustcacerts -alias root -file ca.crt -keystore test_ca.keystore
+ * 
+ * 
+ */
+public class CustomX509TrustManagerTest {
+
+    @Test
+    public void testEmptyDefaultOur() {
+        X509TrustManager tm = new CustomX509TrustManager(
+                (X509TrustManager) null, (X509TrustManager) null);
+        assertEquals(0, tm.getAcceptedIssuers().length);
+        try {
+            tm.checkClientTrusted(null, null);
+        } catch (Exception e) {
+            fail("Should not have thrown exception");
+        }
+        try {
+            tm.checkServerTrusted(null, null);
+            fail("Expected exception since there aren't any trust managers available");
+        } catch (CertificateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testLoadEmptyTrustStoreForOur() {
+        File emptyKeyStore = new File(this.getClass()
+                .getResource("/empty.keystore").getFile());
+        X509TrustManager tm = new CustomX509TrustManager(null, emptyKeyStore,
+                "");
+        assertEquals(0, tm.getAcceptedIssuers().length);
+        try {
+            tm.checkClientTrusted(null, null);
+        } catch (Exception e) {
+            fail("Should not have thrown exception");
+        }
+        try {
+            X509Certificate dummyCert = mock(X509Certificate.class);
+            tm.checkServerTrusted(new X509Certificate[] { dummyCert }, "RSA");
+            fail("Expected exception since there aren't any trust managers available");
+        } catch (CertificateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testLoadEmptyTrustStoreForOurDefaultAsUsual() throws Exception {
+        File emptyKeyStore = new File(this.getClass()
+                .getResource("/empty.keystore").getFile());
+        X509TrustManager tm = new CustomX509TrustManager(emptyKeyStore, "");
+        // Default list should not be empty
+        assertTrue(tm.getAcceptedIssuers().length > 0);
+        try {
+            tm.checkClientTrusted(null, null);
+        } catch (Exception e) {
+            fail("Should not have thrown exception");
+        }
+    }
+    
+    @Test
+    public void canGetCustomCaCertFromOurTrustManager() {
+        File ourKeyStore = new File(this.getClass()
+                .getResource("/test_ca.keystore").getFile());
+        X509TrustManager tm = new CustomX509TrustManager((X509TrustManager)null, ourKeyStore, "testpassword");
+        // keystore contains private key of itself + imported CA cert
+        assertEquals(2, tm.getAcceptedIssuers().length);
+        String issuerNameCustomCA = "1.2.840.113549.1.9.1=#16126a6572626f6161407265646861742e636f6d,CN=test.example.com,O=Red Hat Inc.,L=Saalfelden,ST=Salzburg,C=AT";
+        String issuerNameKeystoreCA = "CN=Unknown,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown";
+        assertEquals(issuerNameCustomCA, tm.getAcceptedIssuers()[0]
+                .getIssuerX500Principal().getName());
+        assertEquals(issuerNameKeystoreCA, tm.getAcceptedIssuers()[1]
+                .getIssuerX500Principal().getName());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/SSLKeystoreConfigurationTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,67 @@
+/*
+ * 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.web.client.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.web.client.internal.SSLKeystoreConfiguration;
+
+public class SSLKeystoreConfigurationTest {
+
+    @Test
+    public void canGetKeystoreFileFromProps() throws Exception {
+        File clientProps = new File(this.getClass().getResource("/client.properties").getFile());
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        String keystorePath = "/path/to/thermostat.keystore";
+        String keystorePwd = "some password";
+        assertEquals(keystorePath, SSLKeystoreConfiguration.getKeystoreFile().getAbsolutePath());
+        assertEquals(keystorePwd, SSLKeystoreConfiguration.getKeyStorePassword());
+    }
+    
+    @Test
+    public void notExistingPropertiesFileReturnsNull() throws Exception {
+        File clientProps = new File("i/am/not/there/file.txt");
+        SSLKeystoreConfiguration.initClientProperties(clientProps);
+        assertTrue(SSLKeystoreConfiguration.getKeystoreFile() == null);
+        assertEquals("", SSLKeystoreConfiguration.getKeyStorePassword());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/TestObj.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,67 @@
+/*
+ * 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.web.client.internal;
+
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.BasePojo;
+
+@Entity
+public class TestObj extends BasePojo {
+
+    
+    private String property1;
+
+    @Persist
+    public void setProperty1(String property1) {
+        this.property1 = property1;
+    }
+
+    @Persist
+    public String getProperty1() {
+        return property1;
+    }
+
+    public boolean equals(Object o) {
+        if (! (o instanceof TestObj)) {
+            return false;
+        }
+        TestObj other = (TestObj) o;
+        return property1.equals(other.property1);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,571 @@
+/*
+ * 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.web.client.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.redhat.thermostat.storage.core.AuthToken;
+import com.redhat.thermostat.storage.core.AuthToken;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Categories;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.test.FreePortFinder;
+import com.redhat.thermostat.test.FreePortFinder.TryPort;
+import com.redhat.thermostat.web.common.Qualifier;
+import com.redhat.thermostat.web.common.WebInsert;
+import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.web.common.WebRemove;
+import com.redhat.thermostat.web.common.WebUpdate;
+
+public class WebStorageTest {
+
+    private Server server;
+
+    private int port;
+
+    private String requestBody;
+
+    private String responseBody;
+    private Map<String,String> headers;
+    private String method;
+    private String requestURI;
+    private int responseStatus;
+
+    private static Category category;
+    private static Key<String> key1;
+    private static Key<Integer> key2;
+
+    private WebStorage storage;
+
+    @BeforeClass
+    public static void setupCategory() {
+        key1 = new Key<>("property1", true);
+        key2 = new Key<>("property2", true);
+        category = new Category("test", key1);
+    }
+
+    @AfterClass
+    public static void cleanupCategory() {
+        Categories.remove(category);
+        category = null;
+        key1 = null;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        
+        port = FreePortFinder.findFreePort(new TryPort() {
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port);
+            }
+        });
+
+        StartupConfiguration config = new StartupConfiguration() {
+            
+            @Override
+            public String getDBConnectionString() {
+                return "http://fluff.example.org";
+            }
+        };
+        storage = new WebStorage(config);
+        storage.setEndpoint("http://localhost:" + port + "/");
+        storage.setAgentId(new UUID(123, 456));
+        headers = new HashMap<>();
+        requestURI = null;
+        method = null;
+        responseStatus = HttpServletResponse.SC_OK;
+        registerCategory();
+    }
+
+    private void startServer(int port) throws Exception {
+        server = new Server(port);
+        server.setHandler(new AbstractHandler() {
+            
+            @Override
+            public void handle(String target, Request baseRequest,
+                    HttpServletRequest request, HttpServletResponse response)
+                    throws IOException, ServletException {
+                Enumeration<String> headerNames = request.getHeaderNames();
+                while (headerNames.hasMoreElements()) {
+                    String headerName = headerNames.nextElement();
+                    headers.put(headerName, request.getHeader(headerName));
+                }
+
+                method = request.getMethod();
+                requestURI = request.getRequestURI();
+
+                // Read request body.
+                StringBuilder body = new StringBuilder();
+                Reader reader = request.getReader();
+                while (true) {
+                    int read = reader.read();
+                    if (read == -1) {
+                        break;
+                    }
+                    body.append((char) read);
+                }
+                requestBody = body.toString();
+                // Send response body.
+                response.setStatus(responseStatus);
+                if (responseBody != null) {
+                    response.getWriter().write(responseBody);
+                }
+                baseRequest.setHandled(true);
+            }
+        });
+        server.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+
+        headers = null;
+        requestURI = null;
+        method = null;
+        storage = null;
+
+        server.stop();
+        server.join();
+    }
+
+    private void registerCategory() {
+
+        // Return 42 for categoryId.
+        Gson gson = new Gson();
+        responseBody = gson.toJson(42);
+
+        storage.registerCategory(category);
+    }
+
+    @Test
+    public void testFindPojo() throws UnsupportedEncodingException, IOException {
+
+        TestObj obj = new TestObj();
+        obj.setProperty1("fluffor");
+        Gson gson = new Gson();
+        responseBody = gson.toJson(obj);
+
+        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        TestObj result = storage.findPojo(query, TestObj.class);
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String[] parts = line.split("=");
+        assertEquals("query", parts[0]);
+        WebQuery restQuery = gson.fromJson(parts[1], WebQuery.class);
+
+        assertEquals(42, restQuery.getCategoryId());
+        List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qual = qualifiers.get(0);
+        assertEquals(new Key<String>("property1", true), qual.getKey());
+        assertEquals(Criteria.EQUALS, qual.getCriteria());
+        assertEquals("fluff", qual.getValue());
+
+        assertEquals("fluffor", result.getProperty1());
+    }
+
+    @Test
+    public void testFindAllPojos() throws UnsupportedEncodingException, IOException {
+
+        TestObj obj1 = new TestObj();
+        obj1.setProperty1("fluffor1");
+        TestObj obj2 = new TestObj();
+        obj2.setProperty1("fluffor2");
+        Gson gson = new Gson();
+        responseBody = gson.toJson(Arrays.asList(obj1, obj2));
+
+        Key<String> key1 = new Key<>("property1", true);
+        Query query = storage.createQuery().from(category).where(key1, Criteria.EQUALS, "fluff");
+
+        Cursor<TestObj> results = storage.findAllPojos(query, TestObj.class);
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String[] parts = line.split("=");
+        assertEquals("query", parts[0]);
+        WebQuery restQuery = gson.fromJson(parts[1], WebQuery.class);
+
+        assertEquals(42, restQuery.getCategoryId());
+        List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qual = qualifiers.get(0);
+        assertEquals(new Key<String>("property1", true), qual.getKey());
+        assertEquals(Criteria.EQUALS, qual.getCriteria());
+        assertEquals("fluff", qual.getValue());
+
+        assertTrue(results.hasNext());
+        assertEquals("fluffor1", results.next().getProperty1());
+        assertTrue(results.hasNext());
+        assertEquals("fluffor2", results.next().getProperty1());
+        assertFalse(results.hasNext());
+    }
+
+    @Test
+    public void testPut() throws IOException, JsonSyntaxException, ClassNotFoundException {
+
+        TestObj obj = new TestObj();
+        obj.setProperty1("fluff");
+
+        storage.putPojo(category, true, obj);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String [] params = line.split("&");
+        assertEquals(2, params.length);
+        String[] parts = params[0].split("=");
+        assertEquals("insert", parts[0]);
+        WebInsert insert = gson.fromJson(parts[1], WebInsert.class);
+        assertEquals(42, insert.getCategoryId());
+        assertEquals(true, insert.isReplace());
+        assertEquals(TestObj.class.getName(), insert.getPojoClass());
+
+        parts = params[1].split("=");
+        assertEquals(2, parts.length);
+        assertEquals("pojo", parts[0]);
+        Object resultObj = gson.fromJson(parts[1], Class.forName(insert.getPojoClass()));
+        assertEquals(obj, resultObj);
+    }
+
+    @Test
+    public void testCreateRemove() {
+        WebRemove remove = (WebRemove) storage.createRemove();
+        assertNotNull(remove);
+        remove = remove.from(category);
+        assertEquals(42, remove.getCategoryId());
+        assertNotNull(remove);
+        remove = remove.where(key1, "test");
+        assertNotNull(remove);
+        List<Qualifier<?>> qualifiers = remove.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qualifier = qualifiers.get(0);
+        assertEquals(key1, qualifier.getKey());
+        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
+        assertEquals("test", qualifier.getValue());
+    }
+
+    @Test
+    public void testRemovePojo() throws UnsupportedEncodingException, IOException {
+        Remove remove = storage.createRemove().from(category).where(key1, "test");
+        storage.removePojo(remove);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String[] parts = line.split("=");
+        assertEquals("remove", parts[0]);
+        WebRemove actualRemove = gson.fromJson(parts[1], WebRemove.class);
+        
+        assertEquals(42, actualRemove.getCategoryId());
+        List<Qualifier<?>> qualifiers = actualRemove.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qualifier = qualifiers.get(0);
+        assertEquals(key1, qualifier.getKey());
+        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
+        assertEquals("test", qualifier.getValue());
+    }
+
+    @Test
+    public void testCreateUpdate() {
+        WebUpdate update = (WebUpdate) storage.createUpdate();
+        assertNotNull(update);
+        update = update.from(category);
+        assertEquals(42, update.getCategoryId());
+        assertNotNull(update);
+        update = update.where(key1, "test");
+        assertNotNull(update);
+        List<Qualifier<?>> qualifiers = update.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qualifier = qualifiers.get(0);
+        assertEquals(key1, qualifier.getKey());
+        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
+        assertEquals("test", qualifier.getValue());
+        update = update.set(key1, "fluff");
+        assertNotNull(update);
+        List<WebUpdate.UpdateValue> updates = update.getUpdates();
+        assertEquals(1, updates.size());
+        assertEquals("fluff", updates.get(0).getValue());
+        assertEquals(key1, updates.get(0).getKey());
+        assertEquals("java.lang.String", updates.get(0).getValueClass());
+    }
+
+    @Test
+    public void testUpdate() throws UnsupportedEncodingException, IOException, JsonSyntaxException, ClassNotFoundException {
+
+        WebUpdate update = storage.createUpdate().from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
+        storage.updatePojo(update);
+
+        Gson gson = new Gson();
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String [] params = line.split("&");
+        assertEquals(2, params.length);
+        String[] parts = params[0].split("=");
+        assertEquals("update", parts[0]);
+        WebUpdate receivedUpdate = gson.fromJson(parts[1], WebUpdate.class);
+        assertEquals(42, receivedUpdate.getCategoryId());
+
+        List<WebUpdate.UpdateValue> updates = receivedUpdate.getUpdates();
+        assertEquals(2, updates.size());
+
+        WebUpdate.UpdateValue update1 = updates.get(0);
+        assertEquals(key1, update1.getKey());
+        assertEquals("java.lang.String", update1.getValueClass());
+        assertNull(update1.getValue());
+
+        WebUpdate.UpdateValue update2 = updates.get(1);
+        assertEquals(key2, update2.getKey());
+        assertEquals("java.lang.Integer", update2.getValueClass());
+        assertNull(update2.getValue());
+
+        List<Qualifier<?>> qualifiers = receivedUpdate.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        Qualifier<?> qualifier = qualifiers.get(0);
+        assertEquals(key1, qualifier.getKey());
+        assertEquals(Criteria.EQUALS, qualifier.getCriteria());
+        assertEquals("test", qualifier.getValue());
+
+        parts = params[1].split("=");
+        assertEquals(2, parts.length);
+        assertEquals("values", parts[0]);
+        JsonParser jsonParser = new JsonParser();
+        JsonArray jsonArray = jsonParser.parse(parts[1]).getAsJsonArray();
+        String value1 = gson.fromJson(jsonArray.get(0), String.class);
+        assertEquals("fluff", value1);
+        int value2 = gson.fromJson(jsonArray.get(1), Integer.class);
+        assertEquals(42, value2);
+    }
+
+    @Test
+    public void testGetCount() throws UnsupportedEncodingException, IOException {
+
+        Gson gson = new Gson();
+        responseBody = gson.toJson(12345);
+
+        long result = storage.getCount(category);
+
+        StringReader reader = new StringReader(requestBody);
+        BufferedReader bufRead = new BufferedReader(reader);
+        String line = URLDecoder.decode(bufRead.readLine(), "UTF-8");
+        String[] parts = line.split("=");
+        assertEquals("category", parts[0]);
+        assertEquals("42", parts[1]);
+        assertEquals(12345, result);
+    }
+
+    @Test
+    public void testSaveFile() {
+        String data = "Hello World";
+        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
+        storage.saveFile("fluff", in);
+        assertEquals("chunked", headers.get("Transfer-Encoding"));
+        String contentType = headers.get("Content-Type");
+        assertTrue(contentType.startsWith("multipart/form-data; boundary="));
+        String boundary = contentType.split("boundary=")[1];
+        String[] lines = requestBody.split("\n");
+        assertEquals("--" + boundary, lines[0].trim());
+        assertEquals("Content-Disposition: form-data; name=\"file\"; filename=\"fluff\"", lines[1].trim());
+        assertEquals("Content-Type: application/octet-stream", lines[2].trim());
+        assertEquals("Content-Transfer-Encoding: binary", lines[3].trim());
+        assertEquals("", lines[4].trim());
+        assertEquals("Hello World", lines[5].trim());
+        assertEquals("--" + boundary + "--", lines[6].trim());
+        
+    }
+
+    @Test
+    public void testLoadFile() throws IOException {
+        responseBody = "Hello World";
+        InputStream in = storage.loadFile("fluff");
+        assertEquals("file=fluff", requestBody.trim());
+        byte[] data = new byte[11];
+        int totalRead = 0;
+        while (totalRead < 11) {
+            int read = in.read(data, totalRead, 11 - totalRead);
+            if (read < 0) {
+                fail();
+            }
+            totalRead += read;
+        }
+        assertEquals("Hello World", new String(data));
+
+    }
+
+    @Test
+    public void testPurge() {
+        storage.purge();
+        assertEquals("POST", method);
+        assertTrue(requestURI.endsWith("/purge"));
+    }
+
+    @Test
+    public void testGenerateToken() throws UnsupportedEncodingException {
+        responseBody = "flufftoken";
+
+        AuthToken authToken = storage.generateToken();
+
+        assertTrue(requestURI.endsWith("/generate-token"));
+        assertEquals("POST", method);
+
+        String[] requestParts = requestBody.split("=");
+        assertEquals("client-token", requestParts[0]);
+        String clientTokenParam = URLDecoder.decode(requestParts[1], "UTF-8");
+        byte[] clientToken = Base64.decodeBase64(clientTokenParam);
+        assertEquals(256, clientToken.length);
+
+        assertTrue(authToken instanceof AuthToken);
+        AuthToken token = (AuthToken) authToken;
+        byte[] tokenBytes = token.getToken();
+        assertEquals("flufftoken", new String(tokenBytes));
+
+        assertTrue(Arrays.equals(clientToken, token.getClientToken()));
+
+        // Send another request and verify that we send a different client-token every time.
+        storage.generateToken();
+
+        requestParts = requestBody.split("=");
+        assertEquals("client-token", requestParts[0]);
+        clientTokenParam = URLDecoder.decode(requestParts[1], "UTF-8");
+        byte[] clientToken2 = Base64.decodeBase64(clientTokenParam);
+        assertFalse(Arrays.equals(clientToken, clientToken2));
+
+    }
+
+    @Test
+    public void canSSLEnableClient() {
+        StartupConfiguration config = new StartupConfiguration() {
+            
+            @Override
+            public String getDBConnectionString() {
+                return "https://onlyHttpsPrefixUsed.example.com";
+            }
+        };
+        storage = new WebStorage(config);
+        HttpClient client = storage.httpClient;
+        SchemeRegistry schemeReg = client.getConnectionManager().getSchemeRegistry();
+        Scheme scheme = schemeReg.getScheme("https");
+        assertNotNull(scheme);
+        assertEquals(443, scheme.getDefaultPort());
+    }
+
+    @Test
+    public void testVerifyToken() throws UnsupportedEncodingException {
+        byte[] token = "stuff".getBytes();
+        byte[] clientToken = "fluff".getBytes();
+        AuthToken authToken = new AuthToken(token, clientToken);
+
+        responseStatus = 200;
+        boolean ok = storage.verifyToken(authToken);
+        assertTrue(requestURI.endsWith("/verify-token"));
+        assertEquals("POST", method);
+        String[] requestParts = requestBody.split("&");
+        assertEquals(2, requestParts.length);
+        String[] clientTokenParts = requestParts[0].split("=");
+        assertEquals(2, clientTokenParts.length);
+        assertEquals("client-token", clientTokenParts[0]);
+        String urlDecoded = URLDecoder.decode(clientTokenParts[1], "UTF-8");
+        String base64decoded = new String(Base64.decodeBase64(urlDecoded));
+        assertEquals("fluff", base64decoded);
+        String[] authTokenParts = requestParts[1].split("=");
+        assertEquals(2, authTokenParts.length);
+        assertEquals("token", authTokenParts[0]);
+        urlDecoded = URLDecoder.decode(authTokenParts[1], "UTF-8");
+        base64decoded = new String(Base64.decodeBase64(urlDecoded));
+        assertEquals("stuff", base64decoded);
+        assertTrue(ok);
+
+        // Try another one in which verification fails.
+        responseStatus = 401;
+        ok = storage.verifyToken(authToken);
+        assertFalse(ok);
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/resources/ca.crt	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF6TCCA9GgAwIBAgIJAMqGn7zFkpycMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJBVDERMA8GA1UECAwIU2FsemJ1cmcxEzARBgNVBAcMClNhYWxmZWxkZW4x
+FTATBgNVBAoMDFJlZCBIYXQgSW5jLjEZMBcGA1UEAwwQdGVzdC5leGFtcGxlLmNv
+bTEhMB8GCSqGSIb3DQEJARYSamVyYm9hYUByZWRoYXQuY29tMB4XDTEyMTIwMzEz
+MTc1M1oXDTE3MTIwMzEzMTc1M1owgYoxCzAJBgNVBAYTAkFUMREwDwYDVQQIDAhT
+YWx6YnVyZzETMBEGA1UEBwwKU2FhbGZlbGRlbjEVMBMGA1UECgwMUmVkIEhhdCBJ
+bmMuMRkwFwYDVQQDDBB0ZXN0LmV4YW1wbGUuY29tMSEwHwYJKoZIhvcNAQkBFhJq
+ZXJib2FhQHJlZGhhdC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQDKdOtT5HmVdFWj05aTqWQPX9L2NA44/69hwsrIwQFHi09DD+BwobaTmRpWg3Tg
+6qr+128AFlj2N8gYHyKELSxt1rsBV1yleTcpywfsmP7XkcZfu9ZKgmB4xyXuK4IQ
+EYxGqeWIdGYFNncjFm7Na3i9aTJZqtGxGf+kkQX2OdzqMbiXg17AYxH21kqMbNPk
+R2mhWUE4sT9Nfj+qO+iUqHCUMPusJY4xi/tGjLA9phih85ShYyqRBXXHiplzDCm+
+0W/puPodPVDo1FdfAwXExYaj8QoKmqCnGT9U++gMQ0b1xkRyuyImdqF9o63HoCMm
+8/R7ye0QTU8yUXwRoujvowSiip6/FAfADe7QyhHmScP9uS2/tNR0daCKUBnkWnJf
++PvbzNCT3V4MTwvg3LvphhU1JKXLSdQiZkSsLLKjnfUFE6BuT2RNFlWsbnuF2NcK
+H8ZDySDFL2LSeROPmi1lV2k+MScG2XOwKQXVQiU5GPgqNhhYvSClIk/kOagBoI74
+Ixq9hDW1viOHwKHZ315M3rNTg3Y4dq4eohUPP9iLmCFEBOW14MGDOMRKhzdFSUnG
+UBVdUiesqi3zwOSZ0EeqU2SgvmQKwvN1imWqsZlvkKV8p9mnFNmF/idbbkq2EWN0
+q/Hg7ekq/6mdN2f3XTGf0F85pTUtWWKpoyUM2qmZ72H+hQIDAQABo1AwTjAdBgNV
+HQ4EFgQUpknaZuFsL+AYna6uu5gSlKVUdbkwHwYDVR0jBBgwFoAUpknaZuFsL+AY
+na6uu5gSlKVUdbkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEAyAlS
+DJ+h4SO0mfZLU88JuRF8LoADD2u2eO890aIC1PtxIm9Osrfrc104LukCGpeLJvSZ
+hee/yZklen6K7lEURCgYmHreDtWx6zkf+qGdhqq0+97cfzvnSH8pDwVJxzY8dr+Q
+K2XSFOszkmVVNOnvu/GgHjcpOvXBNItnalrAqGgFi4cOGRzFl9x4iWUEMiUWERnP
+ntsisNsG4yY8D50Vl/k9qrU02IAsNQ+pnjDI+8bPni4aDE6nGH1+aNmS0J1YBM0s
+pPpK/W9GIxltARUfHI/mnvRNMCPeaG7ohJKBaIJkvibTa5Ux6zx1lNhJmDTHH4t+
+jMdoF5+90/AxYTnOQFb9oTPTX4+HgOhib9YtaxA7mXH5lVGFB19UrT7eFFNZeUFu
+Du+HOCPSrtTgLUdrkix+znl7ai4SORYFGL/v7QDw9U3FCGgRWuKzyQasvm3xELea
+3GXOuttDMWyfsAAsBWGpcJVN+YN23B75j1xN2b8JMvNNQChifWiFcztp7DsA1qxo
+M4g88l6WlRFm7OKqi9ZMbPH9/LlmvAhak5fS4l6UI08v/bX9qUEWNo1RzBZlHA4U
+3Xs9P3VF7h2pp0O3ucWm6nWAcp0CfvmaTnjof+cW8LDRHqEK0tWLEdBLg1TnUnnk
+EUsxCO6b/aOvribtT0aDLRB6sF8MTAtOBkczmp4=
+-----END CERTIFICATE-----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/client/src/test/resources/client.properties	Mon Dec 10 15:05:37 2012 +0100
@@ -0,0 +1,2 @@
+KEYSTORE_FILE=/path/to/thermostat.keystore
+KEYSTORE_PASSWORD=some password
\ No newline at end of file
Binary file web/client/src/test/resources/empty.keystore has changed
Binary file web/client/src/test/resources/test_ca.keystore has changed