changeset 909:2930e0c260cc

Merge
author Elliott Baron <ebaron@redhat.com>
date Mon, 14 Jan 2013 14:04:50 -0500
parents 1e51015e63e5 (current diff) b1b81446c892 (diff)
children 421d8a954893
files agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBOptionParser.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBStartupConfiguration.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/MongoProcessRunner.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StalePidFileException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageAlreadyRunningException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStartException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStopException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java bundles/pom.xml bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/Activator.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleLoader.java bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java bundles/src/test/java/com/redhat/thermostat/bundles/OSGiRegistryTest.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundleLoaderTest.java bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java distribution/pom.xml storage/core/src/main/java/com/redhat/thermostat/storage/model/AgentIdPojo.java web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java
diffstat 147 files changed, 4083 insertions(+), 3427 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/pom.xml	Mon Jan 14 14:04:50 2013 -0500
@@ -106,7 +106,7 @@
             <Private-Package>
               com.redhat.thermostat.agent.cli.impl,
               com.redhat.thermostat.agent.cli.impl.locale,
-              com.redhat.thermostat.agent.cli.db,
+              com.redhat.thermostat.agent.cli.impl.db,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java	Wed Jan 09 14:59:30 2013 -0500
+++ /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.agent.cli.db;
-
-import com.redhat.thermostat.agent.cli.impl.StorageCommand;
-
-/**
- * Set of configuration option that the {@link StorageCommand} understands.
- */
-public enum DBConfig {
-
-    BIND,
-    PORT,
-    
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBOptionParser.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +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.db;
-
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.config.ThermostatOptionParser;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationState;
-
-public class DBOptionParser implements ThermostatOptionParser {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private boolean quiet;
-    
-    private DBStartupConfiguration configuration;
-    
-    private Arguments args;
-
-    private DBArgs serviceAction;
-    
-    private boolean dryRun;
-    
-    public DBOptionParser(DBStartupConfiguration configuration, Arguments args) {
-        this.args = args;
-        this.configuration = configuration;
-    }
-    
-    @Override
-    public void parse() throws InvalidConfigurationException {
-
-        if (args.hasArgument(DBArgs.START.option)) {
-            serviceAction = DBArgs.START;
-        } else if (args.hasArgument(DBArgs.STOP.option)) {
-            serviceAction = DBArgs.STOP;
-        } else {
-            throw new InvalidConfigurationException(translator.localize(LocaleResources.COMMAND_STORAGE_ARGUMENT_REQUIRED));
-        }
-
-        if (args.hasArgument(DBArgs.DRY.option)) {
-            dryRun = true;
-        }
-        
-        if (args.hasArgument(DBArgs.QUIET.option)) {
-            quiet = true;
-        }
-        
-        // leave at the end, since it depends on the previous settings
-        String address = configuration.getBindIP();
-        long port = configuration.getPort();
-        configuration.setDBConnectionString("mongodb://" + address + ":" + port);
-    }
-
-    public boolean isDryRun() {
-        return dryRun;
-    }
-    
-    public ApplicationState getAction() {
-        return serviceAction.state;
-    }
-
-    static enum DBArgs {
-                
-        DRY("dryRun", ApplicationState.NONE),
-        
-        HELP("help", ApplicationState.HELP),
-        
-        START("start", ApplicationState.START),
-        STOP("stop", ApplicationState.STOP),
-        
-        QUIET("quiet", ApplicationState.NONE);
-        
-        private String option;
-        private ApplicationState state;
-        
-        DBArgs(String option, ApplicationState state) {
-            this.option = option;
-            this.state = state;
-        }
-    }
-
-    public boolean isQuiet() {
-        return quiet;
-    }
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBStartupConfiguration.java	Wed Jan 09 14:59:30 2013 -0500
+++ /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.agent.cli.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.config.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-
-public class DBStartupConfiguration implements StartupConfiguration {
-    
-    private File dbPath;
-    private File logFile;
-    private File pidFile;
-        
-    private long localPort;
-    
-    private String dbConnectionString;
-    
-    private String ip;
-        
-    public DBStartupConfiguration() throws InvalidConfigurationException {
-        dbPath = ConfigUtils.getStorageDirectory();
-        logFile = ConfigUtils.getStorageLogFile();
-        pidFile = ConfigUtils.getStoragePidFile();
-    }
-    
-    public File getDBPath() {
-        return dbPath;
-    }
-    
-    public File getLogFile() {
-        return logFile;
-    }
-    
-    public File getPidFile() {
-        return pidFile;
-    }
-   
-    public void setPort(long localPort) {
-        this.localPort = localPort;
-    }
-    
-    public long getPort() {
-        return localPort;
-    }
-    
-    void setDBConnectionString(String dbConnectionString) {
-        this.dbConnectionString = dbConnectionString;
-    }
-    
-    @Override
-    public String getDBConnectionString() {
-        return dbConnectionString;
-    }
-
-    public void setBindIP(String ip) {
-        this.ip = ip;
-    }
-    
-    public String getBindIP() {
-        return ip;
-    }
-}
\ No newline at end of file
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/MongoProcessRunner.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,236 +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.db;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.utils.LoggedExternalProcess;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.service.process.UnixProcessUtilities;
-
-public class MongoProcessRunner {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-    private static final Logger logger = LoggingUtils.getLogger(MongoProcessRunner.class);
-
-    private static final String MONGO_PROCESS = "mongod";
-
-    private static final String [] MONGO_BASIC_ARGS = {
-        "mongod", "--quiet", "--fork", "--auth", "--nohttpinterface", "--bind_ip"
-    };
-
-    private static final String [] MONGO_SHUTDOWN_ARGS = {
-        "kill", "-s", "TERM"
-    };
-
-    private static final String NO_JOURNAL_ARGUMENT = "--nojournal";
-    private static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
-
-    private DBStartupConfiguration configuration;
-    private boolean isQuiet;
-    
-    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
-        this.configuration = configuration;
-        this.isQuiet = quiet;
-    }
-
-    private String getPid() {
-        
-        String pid = null;
-        
-        File pidfile = configuration.getPidFile();
-        Charset charset = Charset.defaultCharset();
-        if (pidfile.exists()) {
-            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
-                pid = reader.readLine();
-                if (pid == null || pid.isEmpty()) {
-                    pid = null;
-                }
-            } catch (IOException ex) {
-                logger.log(Level.WARNING, "Exception while reading pid file", ex);
-                pid = null;
-            }
-        }
-        
-        return pid;
-    }
-    
-    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
- 
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
-        commands.add(getPid());
-
-        LoggedExternalProcess process = new LoggedExternalProcess(commands);
-        int status = process.runAndReturnResult();
-        if (status == 0) {
-            display(translator.localize(LocaleResources.SERVER_SHUTDOWN_COMPLETE, configuration.getDBPath().toString()));
-            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
-            
-        } else {
-            
-            String message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
-                    configuration.getDBPath().toString(),
-                    String.valueOf(status));
-            display(message);
-            throw new StorageStopException(configuration.getDBPath(), status, message);
-        }
-    }
-    
-    private boolean checkExistingProcess() {
-        String pid = getPid();
-        if (pid == null)
-            return false;
-        
-        String processName = UnixProcessUtilities.getInstance().getProcessName(getPid());
-        // TODO: check if we want mongos or mongod from the configs
-        return processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
-    }
-    
-    public void startService() throws IOException, InterruptedException, ApplicationException {
-
-        String pid = getPid();
-        if (pid != null) {
-            String message = null;
-            ApplicationException ex = null;
-            if (!checkExistingProcess()) {
-                message = translator.localize(LocaleResources.STALE_PID_FILE_NO_MATCHING_PROCESS, configuration.getPidFile().toString(), MONGO_PROCESS);
-                ex = new StalePidFileException(configuration.getPidFile());
-            } else {
-                message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
-                ex = new StorageAlreadyRunningException(Integer.valueOf(pid), message);
-            }
-            
-            display(message);
-            throw ex;
-        }
-        
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_BASIC_ARGS));
-        String dbVersion = getDBVersion();
-        if (dbVersion.compareTo(NO_JOURNAL_FIRST_VERSION) >= 0) {
-            commands.add(1, NO_JOURNAL_ARGUMENT);
-        }
-
-        // check that the db directory exist
-        display(translator.localize(LocaleResources.STARTING_STORAGE_SERVER));
-
-        commands.add(configuration.getBindIP());
-
-        commands.add("--dbpath");
-        commands.add(configuration.getDBPath().getCanonicalPath());
-
-        commands.add("--logpath");
-        commands.add(configuration.getLogFile().getCanonicalPath());
-
-        commands.add("--pidfilepath");
-        commands.add(configuration.getPidFile().getCanonicalPath());
-
-        commands.add("--port");
-        commands.add(Long.toString(configuration.getPort()));
-        
-        LoggedExternalProcess process = new LoggedExternalProcess(commands);
-        int status = -1;
-        try {
-            status = process.runAndReturnResult();
-        } catch (ApplicationException ae) {
-            String message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            display(message);
-            throw ae;
-        }
-
-        Thread.sleep(500);
-
-        if (status == 0) {
-            pid = getPid();
-            if (pid == null) status = -1;
-        }
-        
-        if (status == 0) {
-            display(translator.localize(LocaleResources.SERVER_LISTENING_ON, configuration.getDBConnectionString()));
-            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
-            display(translator.localize(LocaleResources.PID_IS,  String.valueOf(pid)));
-            
-        } else {
-            
-            String message = translator.localize(LocaleResources.CANNOT_START_SERVER,
-                             configuration.getDBPath().toString(),
-                             String.valueOf(status));
-            display(message);
-            throw new StorageStartException(configuration.getDBPath(), status, message);
-        }
-    }
- 
-    private String getDBVersion() throws IOException {
-        Process process;
-        try {
-            process = new ProcessBuilder(Arrays.asList("mongod", "--version"))
-                    .start();
-        } catch (IOException e) {
-            String message = translator.localize(
-                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            display(message);
-            throw e;
-        }
-        InputStream out = process.getInputStream();
-        InputStreamReader reader = new InputStreamReader(out);
-        BufferedReader bufReader = new BufferedReader(reader);
-        String firstLine = bufReader.readLine();
-        int commaIdx = firstLine.indexOf(",", 12);
-        String versionString = firstLine.substring(12, commaIdx);
-        return versionString;
-    }
-
-    private void display(String message) {
-        if (!isQuiet) {
-            System.out.println(message);
-        }
-    }
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StalePidFileException.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +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.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-public class StalePidFileException extends ApplicationException {
-
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private final File pidFile;
-
-    public StalePidFileException(File pidFile) {
-        super(translator.localize(LocaleResources.STALE_PID_FILE, pidFile.toString()));
-        this.pidFile = pidFile;
-    }
-
-    public File getPidFile() {
-        return pidFile;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageAlreadyRunningException.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +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.db;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-public class StorageAlreadyRunningException extends ApplicationException {
-
-    private final int storagePid;
-
-    public StorageAlreadyRunningException(int pid, String message) {
-        super(message);
-        storagePid = pid;
-    }
-
-    public int getStoragePid() {
-        return storagePid;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStartException.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-public class StorageStartException extends ApplicationException {
-
-    private final File dbFile;
-    private final int status;
-
-    public StorageStartException(File dbPath, int status, String message) {
-        super(message);
-        this.dbFile = dbPath;
-        this.status = status;
-    }
-
-    public File getDbPath() {
-        return dbFile;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStopException.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-public class StorageStopException extends ApplicationException {
-
-    private final File dbConfig;
-    private final int status;
-
-    public StorageStopException(File dbConfig, int status, String message) {
-        super(message);
-        this.dbConfig = dbConfig;
-        this.status = status;
-    }
-
-    public File getDbConfig() {
-        return dbConfig;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
+import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Mon Jan 14 14:04:50 2013 -0500
@@ -140,7 +140,9 @@
         connection.connect();
         logger.fine("Connecting to storage...");
 
+        @SuppressWarnings("rawtypes")
         ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
+        @SuppressWarnings("unchecked")
         final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
         configServer.startListening(configuration.getConfigListenAddress());
         
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,7 +40,8 @@
 import java.util.List;
 import java.util.concurrent.Semaphore;
 
-import com.redhat.thermostat.agent.cli.db.StorageAlreadyRunningException;
+import com.redhat.thermostat.agent.cli.impl.db.StorageAlreadyRunningException;
+import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +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.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.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;
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBConfig.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.agent.cli.impl.db;
+
+
+/**
+ * Set of configuration options that the {@link StorageCommand} understands.
+ * Keys map to properties in $THERMOSTAT_HOME/storage/db.properties.
+ */
+public enum DBConfig {
+
+    /**
+     * The bind IP address.
+     */
+    BIND,
+    /**
+     * The port on which mongodb will be listening.
+     */
+    PORT,
+    /**
+     * Weather or not to start mongodb with SSL enabled.
+     */
+    SSL_ENABLE,
+    /**
+     * The PEM encoded SSL certificate + SSL key.
+     */
+    SSL_PEM_FILE,
+    /**
+     * The passphrase for the encrypted SSL key. Only used if the private key was encrypted.
+     */
+    SSL_KEY_PASSWORD,
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBOptionParser.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,121 @@
+/*
+ * 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.db;
+
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.config.ThermostatOptionParser;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
+public class DBOptionParser implements ThermostatOptionParser {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private boolean quiet;
+    
+    private DBStartupConfiguration configuration;
+    
+    private Arguments args;
+
+    private DBArgs serviceAction;
+    
+    private boolean dryRun;
+    
+    public DBOptionParser(DBStartupConfiguration configuration, Arguments args) {
+        this.args = args;
+        this.configuration = configuration;
+    }
+    
+    @Override
+    public void parse() throws InvalidConfigurationException {
+
+        if (args.hasArgument(DBArgs.START.option)) {
+            serviceAction = DBArgs.START;
+        } else if (args.hasArgument(DBArgs.STOP.option)) {
+            serviceAction = DBArgs.STOP;
+        } else {
+            throw new InvalidConfigurationException(translator.localize(LocaleResources.COMMAND_STORAGE_ARGUMENT_REQUIRED));
+        }
+
+        if (args.hasArgument(DBArgs.DRY.option)) {
+            dryRun = true;
+        }
+        
+        if (args.hasArgument(DBArgs.QUIET.option)) {
+            quiet = true;
+        }
+        
+        // leave at the end, since it depends on the previous settings
+        String address = configuration.getBindIP();
+        long port = configuration.getPort();
+        configuration.setDBConnectionString("mongodb://" + address + ":" + port);
+    }
+
+    public boolean isDryRun() {
+        return dryRun;
+    }
+    
+    public ApplicationState getAction() {
+        return serviceAction.state;
+    }
+
+    static enum DBArgs {
+                
+        DRY("dryRun", ApplicationState.NONE),
+        
+        HELP("help", ApplicationState.HELP),
+        
+        START("start", ApplicationState.START),
+        STOP("stop", ApplicationState.STOP),
+        
+        QUIET("quiet", ApplicationState.NONE);
+        
+        private String option;
+        private ApplicationState state;
+        
+        DBArgs(String option, ApplicationState state) {
+            this.option = option;
+            this.state = state;
+        }
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfiguration.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,179 @@
+/*
+ * 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.db;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+
+public class DBStartupConfiguration implements StartupConfiguration {
+    
+    private File dbPath;
+    private File logFile;
+    private File pidFile;
+    private boolean sslEnabled = false;
+    private File sslPemFile;
+    private String sslKeyPassphrase;
+        
+    private long localPort;
+    
+    private String dbConnectionString;
+    
+    private String ip;
+        
+    public DBStartupConfiguration(File properties, File dbPath, File logFile,
+            File pidFile) throws InvalidConfigurationException {
+        this.dbPath = dbPath;
+        this.logFile = logFile;
+        this.pidFile = pidFile;
+        readAndSetProperties(properties);
+    }
+    
+    public File getDBPath() {
+        return dbPath;
+    }
+    
+    public File getLogFile() {
+        return logFile;
+    }
+    
+    public File getPidFile() {
+        return pidFile;
+    }
+   
+    public void setPort(long localPort) {
+        this.localPort = localPort;
+    }
+    
+    public long getPort() {
+        return localPort;
+    }
+    
+    void setDBConnectionString(String dbConnectionString) {
+        this.dbConnectionString = dbConnectionString;
+    }
+    
+    @Override
+    public String getDBConnectionString() {
+        return dbConnectionString;
+    }
+
+    void setBindIP(String ip) {
+        this.ip = ip;
+    }
+    
+    public String getBindIP() {
+        return ip;
+    }
+
+    public boolean isSslEnabled() {
+        return sslEnabled;
+    }
+
+    void setSslEnabled(boolean sslEnabled) {
+        this.sslEnabled = sslEnabled;
+    }
+
+    /**
+     * 
+     * @return The file containing the server certificate and the private key in PEM format or null
+     *         if nothing was specified in $THERMOSTAT_HOME/storage/db.properties.
+     */
+    public File getSslPemFile() {
+        return sslPemFile;
+    }
+
+    void setSslPemFile(File sslPemFile) {
+        this.sslPemFile = sslPemFile;
+    }
+
+    /**
+     * 
+     * @return The passphrase for the encrypted server key or null if config was
+     *         not set.
+     */
+    public String getSslKeyPassphrase() {
+        return sslKeyPassphrase;
+    }
+
+    void setSslKeyPassphrase(String sslKeyPassphrase) {
+        this.sslKeyPassphrase = sslKeyPassphrase;
+    }
+    
+    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);
+            setPort(localPort);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.BIND.name())) {
+            String ip = (String) properties.get(DBConfig.BIND.name());
+            setBindIP(ip);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
+        }
+        
+        // optional config
+        String enableSSLConfig = properties.getProperty(DBConfig.SSL_ENABLE.name());
+        setSslEnabled(Boolean.parseBoolean(enableSSLConfig));
+        
+        String pemFile = properties.getProperty(DBConfig.SSL_PEM_FILE.name());
+        if (pemFile != null) {
+            setSslPemFile(new File(pemFile));
+        }
+        
+        String keyPassPhrase = properties.getProperty(DBConfig.SSL_KEY_PASSWORD.name());
+        setSslKeyPassphrase(keyPassPhrase);
+        
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunner.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,266 @@
+/*
+ * 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.db;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.utils.LoggedExternalProcess;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.service.process.UnixProcessUtilities;
+
+public class MongoProcessRunner {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    private static final Logger logger = LoggingUtils.getLogger(MongoProcessRunner.class);
+
+    private static final String MONGO_PROCESS = "mongod";
+
+    private static final String [] MONGO_BASIC_ARGS = {
+        "mongod", "--quiet", "--fork", "--auth", "--nohttpinterface", "--bind_ip"
+    };
+
+    private static final String [] MONGO_SHUTDOWN_ARGS = {
+        "kill", "-s", "TERM"
+    };
+
+    private static final String NO_JOURNAL_ARGUMENT = "--nojournal";
+    private static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
+
+    private DBStartupConfiguration configuration;
+    private boolean isQuiet;
+    
+    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
+        this.configuration = configuration;
+        this.isQuiet = quiet;
+    }
+
+    private String getPid() {
+        
+        String pid = null;
+        
+        File pidfile = configuration.getPidFile();
+        Charset charset = Charset.defaultCharset();
+        if (pidfile.exists()) {
+            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
+                pid = reader.readLine();
+                if (pid == null || pid.isEmpty()) {
+                    pid = null;
+                }
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Exception while reading pid file", ex);
+                pid = null;
+            }
+        }
+        
+        return pid;
+    }
+    
+    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+ 
+        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
+        commands.add(getPid());
+
+        LoggedExternalProcess process = new LoggedExternalProcess(commands);
+        int status = process.runAndReturnResult();
+        if (status == 0) {
+            display(translator.localize(LocaleResources.SERVER_SHUTDOWN_COMPLETE, configuration.getDBPath().toString()));
+            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
+            // all went well, make sure to remove pid file.
+            try {
+                Files.delete(configuration.getPidFile().toPath());
+            } catch (IOException e) {
+                // ignore
+            }
+        } else {
+            
+            String message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
+                    configuration.getDBPath().toString(),
+                    String.valueOf(status));
+            display(message);
+            throw new StorageStopException(configuration.getDBPath(), status, message);
+        }
+    }
+    
+    private boolean checkExistingProcess() {
+        String pid = getPid();
+        if (pid == null)
+            return false;
+        
+        String processName = UnixProcessUtilities.getInstance().getProcessName(getPid());
+        // TODO: check if we want mongos or mongod from the configs
+        return processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
+    }
+    
+    public void startService() throws IOException, InterruptedException,
+            ApplicationException, InvalidConfigurationException {
+
+        String pid = getPid();
+        if (pid != null) {
+            String message = null;
+            if (!checkExistingProcess()) {
+                message = translator.localize(LocaleResources.STALE_PID_FILE_NO_MATCHING_PROCESS, configuration.getPidFile().toString(), MONGO_PROCESS);
+                // Mongo didn't remove its PID file? Work around the issue. Log
+                // the event, remove the stale pid file and continue.
+                logger.log(Level.WARNING, message);
+                try {
+                    Files.delete(configuration.getPidFile().toPath());
+                } catch (IOException benign) {
+                    // ignore this benign error
+                }
+            } else {
+                message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
+                display(message);
+                throw new StorageAlreadyRunningException(Integer.valueOf(pid), message);
+            }
+        }
+        
+        String dbVersion = getDBVersion();
+        List<String> commands = null;
+        commands = getStartupCommand(dbVersion);
+        
+        display(translator.localize(LocaleResources.STARTING_STORAGE_SERVER));
+        
+        LoggedExternalProcess process = new LoggedExternalProcess(commands);
+        int status = -1;
+        try {
+            status = process.runAndReturnResult();
+        } catch (ApplicationException ae) {
+            String message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            display(message);
+            throw ae;
+        }
+
+        Thread.sleep(500);
+
+        if (status == 0) {
+            pid = getPid();
+            if (pid == null) status = -1;
+        }
+        
+        if (status == 0) {
+            display(translator.localize(LocaleResources.SERVER_LISTENING_ON, configuration.getDBConnectionString()));
+            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
+            display(translator.localize(LocaleResources.PID_IS,  String.valueOf(pid)));
+            
+        } else {
+            
+            String message = translator.localize(LocaleResources.CANNOT_START_SERVER,
+                             configuration.getDBPath().toString(),
+                             String.valueOf(status));
+            display(message);
+            throw new StorageStartException(configuration.getDBPath(), status, message);
+        }
+    }
+    
+    List<String> getStartupCommand(String dbVersion) throws IOException, InvalidConfigurationException {
+        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_BASIC_ARGS));
+        
+        if (dbVersion.compareTo(NO_JOURNAL_FIRST_VERSION) >= 0) {
+            commands.add(1, NO_JOURNAL_ARGUMENT);
+        }
+        commands.add(configuration.getBindIP());
+
+        commands.add("--dbpath");
+        commands.add(configuration.getDBPath().getCanonicalPath());
+
+        commands.add("--logpath");
+        commands.add(configuration.getLogFile().getCanonicalPath());
+
+        commands.add("--pidfilepath");
+        commands.add(configuration.getPidFile().getCanonicalPath());
+
+        commands.add("--port");
+        commands.add(Long.toString(configuration.getPort()));
+        
+        if (configuration.isSslEnabled()) {
+            // check for configuration which has a chance of working :)
+            if (configuration.getSslPemFile() == null) {
+                throw new InvalidConfigurationException("No SSL PEM file specified!");
+            } else if (configuration.getSslKeyPassphrase() == null) {
+                throw new InvalidConfigurationException("No SSL key passphrase set!");
+            }
+            commands.add("--sslOnNormalPorts");
+            commands.add("--sslPEMKeyFile");
+            commands.add(configuration.getSslPemFile().getCanonicalPath());
+            commands.add("--sslPEMKeyPassword");
+            commands.add(configuration.getSslKeyPassphrase());
+        }
+        
+        return commands;
+    }
+ 
+    private String getDBVersion() throws IOException {
+        Process process;
+        try {
+            process = new ProcessBuilder(Arrays.asList("mongod", "--version"))
+                    .start();
+        } catch (IOException e) {
+            String message = translator.localize(
+                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            display(message);
+            throw e;
+        }
+        InputStream out = process.getInputStream();
+        InputStreamReader reader = new InputStreamReader(out);
+        BufferedReader bufReader = new BufferedReader(reader);
+        String firstLine = bufReader.readLine();
+        int commaIdx = firstLine.indexOf(",", 12);
+        String versionString = firstLine.substring(12, commaIdx);
+        return versionString;
+    }
+
+    private void display(String message) {
+        if (!isQuiet) {
+            System.out.println(message);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StalePidFileException.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,60 @@
+/*
+ * 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.db;
+
+import java.io.File;
+
+import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StalePidFileException extends ApplicationException {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private final File pidFile;
+
+    public StalePidFileException(File pidFile) {
+        super(translator.localize(LocaleResources.STALE_PID_FILE, pidFile.toString()));
+        this.pidFile = pidFile;
+    }
+
+    public File getPidFile() {
+        return pidFile;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageAlreadyRunningException.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.agent.cli.impl.db;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageAlreadyRunningException extends ApplicationException {
+
+    private final int storagePid;
+
+    public StorageAlreadyRunningException(int pid, String message) {
+        super(message);
+        storagePid = pid;
+    }
+
+    public int getStoragePid() {
+        return storagePid;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,149 @@
+/*
+ * 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.db;
+
+import java.io.File;
+import java.io.IOException;
+
+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 {
+    
+        File dbPath = ConfigUtils.getStorageDirectory();
+        File logFile = ConfigUtils.getStorageLogFile();
+        File pidFile = ConfigUtils.getStoragePidFile();
+        File propertyFile = ConfigUtils.getStorageConfigurationFile();
+        if (!propertyFile.exists()) {
+            throw new InvalidConfigurationException("can't access database configuration file " +
+                                                    propertyFile);
+        }
+        // read everything that is in the configs
+        this.configuration = new DBStartupConfiguration(propertyFile, dbPath, logFile, pidFile);
+        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 (InvalidConfigurationException e) {
+            // rethrow
+            throw e;
+        } catch (Exception e) {
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        }
+    }
+    
+    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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStartException.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,62 @@
+/*
+ * 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.db;
+
+import java.io.File;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageStartException extends ApplicationException {
+
+    private final File dbFile;
+    private final int status;
+
+    public StorageStartException(File dbPath, int status, String message) {
+        super(message);
+        this.dbFile = dbPath;
+        this.status = status;
+    }
+
+    public File getDbPath() {
+        return dbFile;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStopException.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,62 @@
+/*
+ * 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.db;
+
+import java.io.File;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageStopException extends ApplicationException {
+
+    private final File dbConfig;
+    private final int status;
+
+    public StorageStopException(File dbConfig, int status, String message) {
+        super(message);
+        this.dbConfig = dbConfig;
+        this.status = status;
+    }
+
+    public File getDbConfig() {
+        return dbConfig;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+}
--- a/agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/impl/strings.properties	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/impl/strings.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -10,7 +10,7 @@
 CANNOT_START_SERVER = cannot start server {0}, exit status: {1}. Please check that your configuration is valid
 CANNOT_SHUTDOWN_SERVER = cannot shutdown server {0}, exit status: {1}. Please check that your configuration is valid
 STALE_PID_FILE = stale pid file: {0}
-STALE_PID_FILE_NO_MATCHING_PROCESS = A stale pid file ({0}) is present but there is no matching {1} process. Please remove the file if it has been shut down
+STALE_PID_FILE_NO_MATCHING_PROCESS = A stale pid file ({0}) is present but there is no matching {1} process. Removing stale pid file.
 STARTING_STORAGE_SERVER = starting storage server...
 CANNOT_EXECUTE_PROCESS = can not execute {0} process. is it installed?
 SERVER_LISTENING_ON = server listening on ip: {0}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.test.StubBundleContext;
 
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -144,6 +144,7 @@
 
     // FIXME test the rest of AgentApplication
 
+    @SuppressWarnings("serial")
     private static class ThatsAllThatWeCareAbout extends RuntimeException {
 
     }
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,295 +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.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.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 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.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("mongodb://" + BIND + ":" + PORT , conf.getDBConnectionString());
-    }
-    
-    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>() {
-            @SuppressWarnings("incomplete-switch")
-            @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>() {
-            @SuppressWarnings("incomplete-switch")
-            @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));
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfigurationTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.agent.cli.impl.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+
+public class DBStartupConfigurationTest {
+    
+    private File dbLogFile;
+    private File dbPidFile;
+    private File dbPath;
+    
+    @Before
+    public void setUp() {
+        dbLogFile = new File("db.log");
+        dbPidFile = new File("db.pid");
+        dbPath = new File("somepath");
+    }
+    
+    @After
+    public void tearDown() {
+        dbLogFile = null;
+        dbPidFile = null;
+        dbPath = null;
+    }
+
+    @Test
+    public void canGetConfigFromPropertiesFile() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/testDbConfig.properties").getFile());
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, dbPath, dbLogFile, dbPidFile);
+        
+        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
+        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
+        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
+        assertEquals("127.0.0.1", dbConfig.getBindIP());
+        assertEquals(27518, dbConfig.getPort());
+        assertEquals(true, dbConfig.isSslEnabled());
+        assertEquals("/path/to/some/pem/file.pem", dbConfig.getSslPemFile().getAbsolutePath());
+        assertEquals("somepassword", dbConfig.getSslKeyPassphrase());
+    }
+    
+    @Test
+    public void canGetConfigFromPropertiesFile2() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/testDbConfig2.properties").getFile());
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, dbPath, dbLogFile, dbPidFile);
+        
+        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
+        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
+        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
+        assertEquals("127.0.0.1", dbConfig.getBindIP());
+        assertEquals(27518, dbConfig.getPort());
+        assertEquals(false, dbConfig.isSslEnabled());
+        assertNull(dbConfig.getSslPemFile());
+        assertNull(dbConfig.getSslKeyPassphrase());
+    }
+    
+    @Test
+    public void missingBindThrowsConfigException() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/brokenDbConfig.properties").getFile());
+        try {
+            new DBStartupConfiguration(dbProps, dbPath, dbLogFile, dbPidFile);
+            fail("BIND was not specified in properties file");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("BIND property missing", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void missingPortThrowsConfigException() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/brokenDbConfig2.properties").getFile());
+        try {
+            new DBStartupConfiguration(dbProps, dbPath, dbLogFile, dbPidFile);
+            fail("PORT was not specified in properties file");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("PORT property missing", e.getMessage());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunnerTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,123 @@
+package com.redhat.thermostat.agent.cli.impl.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.impl.db.MongoProcessRunner;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+
+public class MongoProcessRunnerTest {
+
+    private MongoProcessRunner runner;
+    private DBStartupConfiguration config;
+    private static final String NO_JOURNAL_MONGODB_VERSION = "2.0.0";
+    private static final String JOURNAL_MONGODB_VERSION = "1.8.0";
+    private static final String BIND_IP = "127.0.0.1";
+    private static final long PORT = 12456;
+    
+    @Before
+    public void setUp() {
+        File dbPath = new File("/path/to/db");
+        File logPath = new File("/path/to/log");
+        File pidFile = new File("/path/to/pid");
+        config = mock(DBStartupConfiguration.class);
+        when(config.getBindIP()).thenReturn(BIND_IP);
+        when(config.getPort()).thenReturn(PORT);
+        when(config.getDBPath()).thenReturn(dbPath);
+        when(config.getLogFile()).thenReturn(logPath);
+        when(config.getPidFile()).thenReturn(pidFile);
+        runner = new MongoProcessRunner(config, false);
+    }
+    
+    @After
+    public void tearDown() {
+        runner = null;
+        config = null;
+    }
+    
+    @Test
+    public void testCommandArgumentsWithJournalVersion() throws Exception {
+        String[] expected = { "mongod", "--nojournal", "--quiet", "--fork",
+                "--auth", "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()) };
+        List<String> cmds = runner.getStartupCommand(NO_JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithNoJournalVersion() throws Exception {
+        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
+                "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()) };
+        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithSSLEnabled() throws Exception {
+        when(config.isSslEnabled()).thenReturn(true);
+        File pemFile = new File("/path/to/cert_and_key.pem");
+        when(config.getSslPemFile()).thenReturn(pemFile);
+        when(config.getSslKeyPassphrase()).thenReturn("non-null");
+        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
+                "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()), "--sslOnNormalPorts",
+                "--sslPEMKeyFile", config.getSslPemFile().getCanonicalPath(),
+                "--sslPEMKeyPassword", config.getSslKeyPassphrase()
+        };
+        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithSSLEnabledThrowsInvalidConfigException() throws IOException {
+        when(config.isSslEnabled()).thenReturn(true);
+        // PEM file can't be null when SSL == true
+        when(config.getSslPemFile()).thenReturn(null);
+        try {
+            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+            fail("Should have thrown exception!");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("No SSL PEM file specified!", e.getMessage());
+        }
+        // Key password can't be null when SSL == true and keyfile present
+        File pemFile = new File("/path/to/ssl.pem");
+        when(config.getSslPemFile()).thenReturn(pemFile);
+        when(config.getSslKeyPassphrase()).thenReturn(null);
+        try {
+            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+            fail("Should have thrown exception!");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("No SSL key passphrase set!", e.getMessage());
+        }
+    }
+
+    private void verifyEquals(String[] expected, String[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i=0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,293 @@
+/*
+ * 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.db;
+
+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.impl.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 StorageCommandTest {
+    
+    private static final String PORT = "27518";
+    private static final String BIND = "127.0.0.1";
+    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.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("mongodb://" + BIND + ":" + PORT , conf.getDBConnectionString());
+    }
+    
+    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>() {
+            @SuppressWarnings("incomplete-switch")
+            @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>() {
+            @SuppressWarnings("incomplete-switch")
+            @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));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/resources/brokenDbConfig.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,2 @@
+PORT=27518
+# missing BIND
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/resources/brokenDbConfig2.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,2 @@
+#PORT=27518
+BIND=127.0.0.1
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/resources/testDbConfig.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,7 @@
+PORT=27518
+BIND=127.0.0.1
+
+## SSL config fluff
+SSL_ENABLE=true
+SSL_PEM_FILE=/path/to/some/pem/file.pem
+SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/resources/testDbConfig2.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,7 @@
+PORT=27518
+BIND=127.0.0.1
+
+## SSL config fluff
+#SSL_ENABLE=true
+#SSL_PEM_FILE=/path/to/some/pem/file.pem
+#SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- a/bundles/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +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</artifactId>
-    <version>0.5.0-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>thermostat-bundles</artifactId>
-  <packaging>bundle</packaging>
-
-  <name>Service providing entry point to OSGi-land</name>
-
-  <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.powermock</groupId>
-      <artifactId>powermock-api-mockito</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-module-junit4</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.jboss.netty</groupId>
-      <artifactId>netty</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-      <scope>provided</scope>
-    </dependency>
-
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-core</artifactId>
-      <version>${project.version}</version>
-      <type>jar</type>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.bundles.impl.Activator</Bundle-Activator>
-            <Bundle-SymbolicName>com.redhat.thermostat.bundles.core</Bundle-SymbolicName>
-            <Export-Package>
-              com.redhat.thermostat.bundles
-            </Export-Package>
-            <Private-Package>
-              com.redhat.thermostat.bundles.impl
-            </Private-Package>
-            <!-- Do not autogenerate uses clauses in Manifests -->
-            <_nouses>true</_nouses>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-</project>
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/OSGiRegistry.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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.bundles;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.osgi.framework.BundleException;
-import org.osgi.framework.launch.Framework;
-
-import com.redhat.thermostat.bundles.impl.BundleLoader;
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-/**
- * A Service that provides features to load bundles for given command names.
- */
-public abstract class OSGiRegistry {
-
-    public abstract void setPrintOSGiInfo(boolean printOSGiInfo);
-
-    public abstract void setCommandInfoSource(CommandInfoSource source);
-
-    public abstract void addBundlesFor(String commandName) throws BundleException, CommandInfoNotFoundException, IOException;
-
-    public static void preLoadBundles(Framework framework, List<String> bundleLocations,
-            boolean printOSGiInfo) throws BundleException {
-        BundleLoader loader = new BundleLoader(printOSGiInfo);
-        loader.installAndStartBundles(framework, bundleLocations);
-    }
-
-    public abstract Configuration getConfiguration();
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/Activator.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +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.bundles.impl;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-import com.redhat.thermostat.bundles.OSGiRegistry;
-import com.redhat.thermostat.common.Configuration;
-
-public class Activator implements BundleActivator {
-
-    ServiceRegistration reg;
-
-    @Override
-    public void start(BundleContext context) throws Exception {
-        OSGiRegistryImpl bundleRegistry = new OSGiRegistryImpl(new Configuration());
-        reg = context.registerService(OSGiRegistry.class.getName(), bundleRegistry, null);
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (reg != null) {
-            reg.unregister();
-            reg = null;
-        }
-    }
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/BundleLoader.java	Wed Jan 09 14:59:30 2013 -0500
+++ /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.bundles.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.launch.Framework;
-
-public class BundleLoader {
-
-    private boolean printOSGiInfo = false;
-
-    BundleLoader() {
-        this(false);
-    }
-
-    public BundleLoader(boolean printOSGiInfo) {
-        setPrintOSGiInfo(printOSGiInfo);
-    }
-
-    public void setPrintOSGiInfo(boolean printOSGiInfo) {
-        this.printOSGiInfo = printOSGiInfo;
-    }
-
-    public List<Bundle> installAndStartBundles(Framework framework,
-            List<String>bundleLocations) throws BundleException {
-        List<Bundle> bundles = new ArrayList<>();
-        BundleContext ctx = framework.getBundleContext();
-        for (String location : bundleLocations) {
-            Bundle bundle = ctx.installBundle(location);
-            if (printOSGiInfo) {
-                System.out.println("BundleLoader: installed bundle: \"" + 
-                        location + "\" as id " + bundle.getBundleId());
-            }
-            bundles.add(bundle);
-        }
-        startBundles(bundles);
-        return bundles;
-    }
-
-    private void startBundles(List<Bundle> bundles) throws BundleException {
-        for (Bundle bundle : bundles) {
-
-            if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
-                if (printOSGiInfo) {
-                    System.out.println("BundleLoader: bundle \"" + bundle.getBundleId() + "\" is a fragment; not starting it");
-                }
-                continue;
-            }
-
-            if (printOSGiInfo) {
-                System.out.println("BundleLoader: starting bundle: \"" + bundle.getBundleId() + "\"");
-            }
-            // We don't want for the framework to set the auto-start bit. Thus, passing
-            // START_TRANSIENT explicitly
-            bundle.start(Bundle.START_TRANSIENT);
-        }
-    }
-
-}
--- a/bundles/src/main/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ /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.bundles.impl;
-
-import com.redhat.thermostat.bundles.OSGiRegistry;
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.ConfigurationException;
-import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-
-public class OSGiRegistryImpl extends OSGiRegistry {
-
-    private CommandInfoSource commandInfos;
-    private Map<String, Bundle> loaded;
-    private Configuration configuration;
-    private BundleLoader loader;
-
-    OSGiRegistryImpl(Configuration configuration) throws ConfigurationException, FileNotFoundException, IOException {
-        initLoadedBundles();
-        this.configuration = configuration;
-        loader = new BundleLoader(configuration.getPrintOSGiInfo());
-    }
-
-    private void initLoadedBundles() {
-        loaded = new HashMap<>();
-        Framework framework = getFramework(this.getClass());
-        for (Bundle bundle: framework.getBundleContext().getBundles()) {
-            loaded.put(bundle.getLocation(), bundle);
-        }
-    }
-
-    @Override
-    public void setPrintOSGiInfo(boolean printOSGiInfo) {
-        configuration.setPrintOSGiInfo(printOSGiInfo);
-        loader.setPrintOSGiInfo(printOSGiInfo);
-    }
-
-    @Override
-    public void setCommandInfoSource(CommandInfoSource source) {
-        this.commandInfos = source;
-    }
-
-    @Override
-    public void addBundlesFor(String commandName) throws BundleException, IOException, CommandInfoNotFoundException {
-        if (configuration.getPrintOSGiInfo()) {
-            System.out.println("Loading additional bundles for: " + commandName);
-        }
-        List<String> requiredBundles = commandInfos.getCommandInfo(commandName).getDependencyResourceNames();
-        List<String> bundlesToLoad = new ArrayList<>();
-        if (requiredBundles != null) {
-            for (String resource : requiredBundles) {
-                if (!isBundleActive(resource)) {
-                    bundlesToLoad.add(resource);
-                }
-            }
-        }
-        Framework framework = getFramework(this.getClass());
-        List<Bundle> successBundles = loader.installAndStartBundles(framework, bundlesToLoad);
-        for (Bundle bundle : successBundles) {
-            loaded.put(bundle.getLocation(), bundle);
-        }
-    }
-
-    private boolean isBundleActive(String location) {
-        Bundle bundle = loaded.get(location);
-        return (bundle != null) && (bundle.getState() == Bundle.ACTIVE);
-    }
-
-    private Framework getFramework(Class<?> cls) {
-        return (Framework) FrameworkUtil.getBundle(cls).getBundleContext().getBundle(0);
-    }
-
-    @Override
-    public Configuration getConfiguration() {
-        return configuration;
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/OSGiRegistryTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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.bundles;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
-
-import java.util.ArrayList;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.launch.Framework;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.bundles.impl.BundleLoader;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(OSGiRegistry.class)
-public class OSGiRegistryTest {
-
-    @Test
-    public void testPreLoadBundles() throws Exception {
-        Framework framework = mock(Framework.class);
-        ArrayList<String> bundleLocations = new ArrayList<>();
-        BundleLoader loader = mock(BundleLoader.class);
-        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
-                withArguments(any()).thenReturn(loader);
-
-        OSGiRegistry.preLoadBundles(framework, bundleLocations, true);
-        verify(loader).installAndStartBundles(framework, bundleLocations);
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/BundleLoaderTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +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.bundles.impl;
-
-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.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import org.junit.Test;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.launch.Framework;
-
-import com.redhat.thermostat.test.Bug;
-
-public class BundleLoaderTest {
-
-    @Test
-    public void verifyBundlesAreInstalledAndStarted() throws BundleException {
-        final String BUNDLE_LOCATION = "bundle-location-1";
-
-        Bundle bundle = mock(Bundle.class);
-        when(bundle.getHeaders()).thenReturn(new Hashtable<>());
-        BundleContext bundleContext = mock(BundleContext.class);
-        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
-        Framework framework = mock(Framework.class);
-        when(framework.getBundleContext()).thenReturn(bundleContext);
-        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
-
-        BundleLoader loader = new BundleLoader();
-        loader.installAndStartBundles(framework, bundleLocations);
-
-        verify(bundle).start(Bundle.START_TRANSIENT);
-    }
-
-    @Bug(id="1227",
-         summary="Make sure launcher does not start fragments",
-         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1227")
-    @Test
-    public void verifyFragmentsAreInstalledButNotStarted() throws BundleException {
-        final String BUNDLE_LOCATION = "bundle-location-1";
-
-        Bundle bundle = mock(Bundle.class);
-        Dictionary<String, String> bundleHeaders = new Hashtable<>();
-        bundleHeaders.put(Constants.FRAGMENT_HOST, "foo-bar");
-        when(bundle.getHeaders()).thenReturn(bundleHeaders);
-
-        BundleContext bundleContext = mock(BundleContext.class);
-        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
-        Framework framework = mock(Framework.class);
-        when(framework.getBundleContext()).thenReturn(bundleContext);
-        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
-
-        BundleLoader loader = new BundleLoader();
-        loader.installAndStartBundles(framework, bundleLocations);
-
-        verify(bundle, times(0)).start(Bundle.START_TRANSIENT);
-
-    }
-}
--- a/bundles/src/test/java/com/redhat/thermostat/bundles/impl/OSGiRegistryImplTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +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.bundles.impl;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-import static org.powermock.api.mockito.PowerMockito.mockStatic;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.cli.CommandInfo;
-import com.redhat.thermostat.common.cli.CommandInfoSource;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({OSGiRegistryImpl.class, FrameworkUtil.class})
-public class OSGiRegistryImplTest {
-
-    private static final String cmdName = "one";
-
-    private static final String jar1Name = "/one.jar";
-    private static final String jar2Name = "/two.jar";
-    private static final String jar3Name = "/three.jar";
-
-    private Bundle b1, b2, b3;
-    private List<String> bundleLocs;
-
-    private BundleLoader loader;
-    private Configuration conf;
-    
-    @Before
-    public void setUp() throws Exception {
-        conf = mock(Configuration.class);
-        when(conf.getThermostatHome()).thenReturn("no_matter");
-        bundleLocs = Arrays.asList(jar1Name, jar2Name, jar3Name);
-        b1 = mock(Bundle.class);
-        when(b1.getLocation()).thenReturn(jar1Name);
-        when(b1.getState()).thenReturn(Bundle.ACTIVE);
-        b2 = mock(Bundle.class);
-        when(b2.getLocation()).thenReturn(jar2Name);
-        when(b2.getState()).thenReturn(Bundle.ACTIVE);
-        b3 = mock(Bundle.class);
-        when(b3.getLocation()).thenReturn(jar3Name);
-        when(b3.getState()).thenReturn(Bundle.ACTIVE);
-        List<Bundle> installed = Arrays.asList(b1, b2, b3);
-
-        loader = mock(BundleLoader.class);
-        when(loader.installAndStartBundles(any(Framework.class), eq(bundleLocs))).
-                thenReturn(installed);
-        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
-                withArguments(any()).thenReturn(loader);
-    }
-
-    @Test
-    public void testLoadBundlesFor() throws Exception {
-        verifyBundlesLoaded(new Bundle[] {}, bundleLocs);
-    }
-
-    @Test
-    public void verifyAlreadyLoadedBundlesNotReloaded() throws Exception {
-        verifyBundlesLoaded(new Bundle[] {b1, b2}, Arrays.asList(jar3Name));
-    }
-
-    private void verifyBundlesLoaded(Bundle[] preloaded, List<String> locationsNeeded) throws Exception {
-        Bundle theBundle = b2;
-        BundleContext theContext = mock(BundleContext.class);
-        when(theContext.getBundles()).thenReturn(preloaded);
-        Framework theFramework = mock(Framework.class);
-        when(theFramework.getBundleContext()).thenReturn(theContext);
-        when(theContext.getBundle(0)).thenReturn(theFramework);
-        when(theBundle.getBundleContext()).thenReturn(theContext);
-        mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
-
-        OSGiRegistryImpl registry = new OSGiRegistryImpl(conf);
-        CommandInfoSource infos = mock(CommandInfoSource.class);
-        CommandInfo info = mock(CommandInfo.class);
-        when (info.getDependencyResourceNames()).thenReturn(bundleLocs);
-        when (infos.getCommandInfo(cmdName)).thenReturn(info);
-        registry.setCommandInfoSource(infos);
-        registry.addBundlesFor(cmdName);
-        verify(loader).installAndStartBundles(any(Framework.class), eq(locationsNeeded));
-    }
-
-    @Test
-    public void verifySetOSGiVerbosityByReflection() throws Exception {
-
-        // All this fluff is just so constructor doesn't NPE.
-        Bundle theBundle = b2;
-        BundleContext theContext = mock(BundleContext.class);
-        when(theContext.getBundles()).thenReturn(new Bundle[]{});
-        Framework theFramework = mock(Framework.class);
-        when(theFramework.getBundleContext()).thenReturn(theContext);
-        when(theContext.getBundle(0)).thenReturn(theFramework);
-        when(theBundle.getBundleContext()).thenReturn(theContext);
-        mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
-
-        Object registry = new OSGiRegistryImpl(conf);
-        Class clazz = registry.getClass();
-        Method m = clazz.getMethod("setPrintOSGiInfo", Boolean.TYPE);
-        m.invoke(registry, true); // If this fails, then API has changed in ways that break FrameworkProvider.
-    }
-
-}
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -49,7 +49,7 @@
     static final Key<Boolean> ALIVE_KEY = new Key<>("alive", false);
     static final Key<String> CONFIG_LISTEN_ADDRESS = new Key<>("configListenAddress", false);
 
-    static final Category CATEGORY = new Category("agent-config",
+    static final Category<AgentInformation> CATEGORY = new Category<>("agent-config", AgentInformation.class,
             Key.AGENT_ID,
             START_TIME_KEY,
             STOP_TIME_KEY,
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/AgentInfoDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Storage;
@@ -64,8 +65,8 @@
 
     @Override
     public List<AgentInformation> getAllAgentInformation() {
-        Query query = storage.createQuery().from(CATEGORY);
-        Cursor<AgentInformation> agentCursor = storage.findAllPojos(query, AgentInformation.class);
+        Query<AgentInformation> query = storage.createQuery(CATEGORY);
+        Cursor<AgentInformation> agentCursor = query.execute();
 
         List<AgentInformation> results = new ArrayList<>();
 
@@ -78,11 +79,10 @@
 
     @Override
     public List<AgentInformation> getAliveAgents() {
-        Query query = storage.createQuery()
-                .from(CATEGORY)
-                .where(AgentInfoDAO.ALIVE_KEY, Criteria.EQUALS, true);
+        Query<AgentInformation> query = storage.createQuery(CATEGORY);
+        query.where(AgentInfoDAO.ALIVE_KEY, Criteria.EQUALS, true);
 
-        Cursor<AgentInformation> agentCursor = storage.findAllPojos(query, AgentInformation.class);
+        Cursor<AgentInformation> agentCursor = query.execute();
 
         List<AgentInformation> results = new ArrayList<>();
 
@@ -95,16 +95,17 @@
 
     @Override
     public AgentInformation getAgentInformation(HostRef agentRef) {
-        Query query = storage.createQuery()
-                .from(CATEGORY)
-                .where(Key.AGENT_ID, Criteria.EQUALS, agentRef.getAgentId());
-
-        return storage.findPojo(query, AgentInformation.class);
+        Query<AgentInformation> query = storage.createQuery(CATEGORY);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, agentRef.getAgentId());
+        query.limit(1);
+        return query.execute().next();
     }
 
     @Override
     public void addAgentInformation(AgentInformation agentInfo) {
-        storage.putPojo(AgentInfoDAO.CATEGORY, true, agentInfo);
+        Put replace = storage.createReplace(CATEGORY);
+        replace.setPojo(agentInfo);
+        replace.apply();
     }
 
     @Override
@@ -115,12 +116,13 @@
 
     @Override
     public void updateAgentInformation(AgentInformation agentInfo) {
-        Update update = storage.createUpdate().from(CATEGORY).where(Key.AGENT_ID, agentInfo.getAgentId())
-                                .set(START_TIME_KEY, agentInfo.getStartTime())
-                                .set(STOP_TIME_KEY, agentInfo.getStopTime())
-                                .set(ALIVE_KEY, agentInfo.isAlive())
-                                .set(CONFIG_LISTEN_ADDRESS, agentInfo.getConfigListenAddress());
-        storage.updatePojo(update);
+        Update update = storage.createUpdate(CATEGORY);
+        update.where(Key.AGENT_ID, agentInfo.getAgentId());
+        update.set(START_TIME_KEY, agentInfo.getStartTime());
+        update.set(STOP_TIME_KEY, agentInfo.getStopTime());
+        update.set(ALIVE_KEY, agentInfo.isAlive());
+        update.set(CONFIG_LISTEN_ADDRESS, agentInfo.getConfigListenAddress());
+        update.apply();
     }
 
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -51,7 +51,7 @@
     static final Key<List<Integer>> PIDS_TO_MONITOR = new Key<>("pids", false);
     static final Key<Integer> ORDER_VALUE = new Key<>("orderValue", false);
 
-    static final Category CATEGORY = new Category("backend-info",
+    static final Category<BackendInformation> CATEGORY = new Category<>("backend-info", BackendInformation.class,
             Key.AGENT_ID,
             BACKEND_NAME,
             BACKEND_DESCRIPTION,
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/BackendInfoDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -43,6 +43,7 @@
 
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Remove;
@@ -61,12 +62,11 @@
     @Override
     public List<BackendInformation> getBackendInformation(HostRef host) {
         // Sort by order value
-        Query query = storage.createQuery()
-                .from(CATEGORY)
-                .where(Key.AGENT_ID, Criteria.EQUALS, host.getAgentId());
+        Query<BackendInformation> query = storage.createQuery(CATEGORY);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, host.getAgentId());
 
         List<BackendInformation> results = new ArrayList<>();
-        Cursor<BackendInformation> cursor = storage.findAllPojos(query, BackendInformation.class);
+        Cursor<BackendInformation> cursor = query.execute();
         while (cursor.hasNext()) {
             BackendInformation backendInfo = cursor.next();
             results.add(backendInfo);
@@ -93,7 +93,9 @@
 
     @Override
     public void addBackendInformation(BackendInformation info) {
-        storage.putPojo(BackendInfoDAO.CATEGORY, false, info);
+        Put add = storage.createAdd(BackendInfoDAO.CATEGORY);
+        add.setPojo(info);
+        add.apply();
     }
 
     @Override
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -51,7 +51,7 @@
     static Key<String> cpuModelKey = new Key<>("cpuModel", false);
     static Key<Long> hostMemoryTotalKey = new Key<>("totalMemory", false);
 
-    static final Category hostInfoCategory = new Category("host-info",
+    static final Category<HostInfo> hostInfoCategory = new Category<>("host-info", HostInfo.class,
             Key.AGENT_ID, hostNameKey, osNameKey, osKernelKey,
             cpuCountKey, cpuModelKey, hostMemoryTotalKey);
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostInfoDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,6 +42,7 @@
 
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Query.Criteria;
@@ -62,21 +63,23 @@
 
     @Override
     public HostInfo getHostInfo(HostRef ref) {
-        Query query = storage.createQuery()
-                .from(hostInfoCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
-        HostInfo result = storage.findPojo(query, HostInfo.class);
+        Query<HostInfo> query = storage.createQuery(hostInfoCategory);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
+        query.limit(1);
+        HostInfo result = query.execute().next();
         return result;
     }
 
     @Override
     public void putHostInfo(HostInfo info) {
-        storage.putPojo(hostInfoCategory, false, info);
+        Put add = storage.createAdd(hostInfoCategory);
+        add.setPojo(info);
+        add.apply();
     }
 
     @Override
     public Collection<HostRef> getHosts() {
-        Query allHosts = storage.createQuery().from(hostInfoCategory);
+        Query<HostInfo> allHosts = storage.createQuery(hostInfoCategory);
         return getHosts(allHosts);
     }
 
@@ -85,10 +88,8 @@
         List<HostRef> hosts = new ArrayList<>();
         List<AgentInformation> agentInfos = agentInfoDao.getAliveAgents();
         for (AgentInformation agentInfo : agentInfos) {
-            Query filter = storage.createQuery()
-                    .from(hostInfoCategory)
-                    .where(Key.AGENT_ID, Criteria.EQUALS, agentInfo.getAgentId());
-
+            Query<HostInfo> filter = storage.createQuery(hostInfoCategory);
+            filter.where(Key.AGENT_ID, Criteria.EQUALS, agentInfo.getAgentId());
             hosts.addAll(getHosts(filter));
         }
 
@@ -96,10 +97,10 @@
     }
 
 
-    private Collection<HostRef> getHosts(Query filter) {
+    private Collection<HostRef> getHosts(Query<HostInfo> filter) {
         Collection<HostRef> hosts = new ArrayList<HostRef>();
         
-        Cursor<HostInfo> hostsCursor = storage.findAllPojos(filter, HostInfo.class);
+        Cursor<HostInfo> hostsCursor = filter.execute();
         while(hostsCursor.hasNext()) {
             HostInfo host = hostsCursor.next();
             String agentId = host.getAgentId();
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetter.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,22 +50,20 @@
 public class HostLatestPojoListGetter<T extends TimeStampedPojo> {
 
     private final Storage storage;
-    private final Category cat;
-    private final Class<T> resultClass;
+    private final Category<T> cat;
 
-    public HostLatestPojoListGetter(Storage storage, Category cat, Class<T> resultClass) {
+    public HostLatestPojoListGetter(Storage storage, Category<T> cat) {
         this.storage = storage;
         this.cat = cat;
-        this.resultClass = resultClass;
     }
 
     public List<T> getLatest(HostRef hostRef, long since) {
-        Query query = buildQuery(hostRef, since);
+        Query<T> query = buildQuery(hostRef, since);
         return getLatest(query);
     }
 
-    private List<T> getLatest(Query query) {
-        Cursor<T> cursor = storage.findAllPojos(query, resultClass);
+    private List<T> getLatest(Query<T> query) {
+        Cursor<T> cursor = query.execute();
         List<T> result = new ArrayList<>();
         while (cursor.hasNext()) {
             T pojo = cursor.next();
@@ -74,13 +72,11 @@
         return result;
     }
 
-    protected Query buildQuery(HostRef hostRef, long since) {
-        Query query = storage.createQuery()
-                .from(cat)
-                .where(Key.AGENT_ID, Criteria.EQUALS, hostRef.getAgentId())
-                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since)
-                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
-        
+    protected Query<T> buildQuery(HostRef hostRef, long since) {
+        Query<T> query = storage.createQuery(cat);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, hostRef.getAgentId());
+        query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         return query;
     }
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -48,7 +48,7 @@
     static Key<String> ip4AddrKey = new Key<>("ip4Addr", false);
     static Key<String> ip6AddrKey = new Key<>("ip6Addr", false);
 
-    static final Category networkInfoCategory = new Category("network-info",
+    static final Category<NetworkInterfaceInfo> networkInfoCategory = new Category<>("network-info", NetworkInterfaceInfo.class,
             Key.AGENT_ID, ifaceKey, ip4AddrKey, ip6AddrKey);
 
     public List<NetworkInterfaceInfo> getNetworkInterfaces(HostRef ref);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,9 +41,10 @@
 
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
 
 class NetworkInterfaceInfoDAOImpl implements NetworkInterfaceInfoDAO {
@@ -57,11 +58,10 @@
 
     @Override
     public List<NetworkInterfaceInfo> getNetworkInterfaces(HostRef ref) {
-        Query allHostNetworkInterfaces = storage.createQuery()
-                .from(networkInfoCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
+        Query<NetworkInterfaceInfo> allHostNetworkInterfaces = storage.createQuery(networkInfoCategory);
+        allHostNetworkInterfaces.where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
 
-        Cursor<NetworkInterfaceInfo> cursor = storage.findAllPojos(allHostNetworkInterfaces, NetworkInterfaceInfo.class);
+        Cursor<NetworkInterfaceInfo> cursor = allHostNetworkInterfaces.execute();
         List<NetworkInterfaceInfo> result = new ArrayList<>();
         while (cursor.hasNext()) {
             NetworkInterfaceInfo stat = cursor.next();
@@ -72,7 +72,9 @@
 
     @Override
     public void putNetworkInterfaceInfo(NetworkInterfaceInfo info) {
-        storage.putPojo(networkInfoCategory, true, info);
+        Put replace = storage.createReplace(networkInfoCategory);
+        replace.setPojo(info);
+        replace.apply();
     }
 
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -61,7 +61,7 @@
     static final Key<Long> startTimeKey = new Key<>("startTimeStamp", false);
     static final Key<Long> stopTimeKey = new Key<>("stopTimeStamp", false);
 
-    static final Category vmInfoCategory = new Category("vm-info",
+    static final Category<VmInfo> vmInfoCategory = new Category<>("vm-info", VmInfo.class,
             Key.AGENT_ID, Key.VM_ID, vmPidKey, runtimeVersionKey, javaHomeKey,
             mainClassKey, commandLineKey,
             vmArgumentsKey, vmNameKey, vmInfoKey, vmVersionKey,
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmInfoDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,10 +42,11 @@
 
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmInfo;
 
 class VmInfoDAOImpl implements VmInfoDAO {
@@ -59,11 +60,11 @@
 
     @Override
     public VmInfo getVmInfo(VmRef ref) {
-        Query findMatchingVm = storage.createQuery()
-                .from(vmInfoCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId())
-                .where(Key.VM_ID, Criteria.EQUALS, ref.getId());
-        VmInfo result = storage.findPojo(findMatchingVm, VmInfo.class);
+        Query<VmInfo> findMatchingVm = storage.createQuery(vmInfoCategory);
+        findMatchingVm.where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId());
+        findMatchingVm.where(Key.VM_ID, Criteria.EQUALS, ref.getId());
+        findMatchingVm.limit(1);
+        VmInfo result = findMatchingVm.execute().next();
         if (result == null) {
             throw new DAOException("Unknown VM: host:" + ref.getAgent().getAgentId() + ";vm:" + ref.getId());
         }
@@ -73,15 +74,14 @@
     @Override
     public Collection<VmRef> getVMs(HostRef host) {
 
-        Query query = buildQuery(host);
-        Cursor<VmInfo> cursor = storage.findAllPojos(query, VmInfo.class);
+        Query<VmInfo> query = buildQuery(host);
+        Cursor<VmInfo> cursor = query.execute();
         return buildVMsFromQuery(cursor, host);
     }
 
-    private Query buildQuery(HostRef host) {
-        Query query = storage.createQuery()
-                .from(vmInfoCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, host.getAgentId());
+    private Query<VmInfo> buildQuery(HostRef host) {
+        Query<VmInfo> query = storage.createQuery(vmInfoCategory);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, host.getAgentId());
         return query;
     }
 
@@ -111,15 +111,17 @@
 
     @Override
     public void putVmInfo(VmInfo info) {
-        storage.putPojo(vmInfoCategory, true, info);
+        Put replace = storage.createReplace(vmInfoCategory);
+        replace.setPojo(info);
+        replace.apply();
     }
 
     @Override
     public void putVmStoppedTime(int vmId, long timestamp) {
-        Update update = storage.createUpdate().from(vmInfoCategory)
-                                              .where(Key.VM_ID, vmId)
-                                              .set(VmInfoDAO.stopTimeKey, timestamp);
-        storage.updatePojo(update);
+        Update update = storage.createUpdate(vmInfoCategory);
+        update.where(Key.VM_ID, vmId);
+        update.set(VmInfoDAO.stopTimeKey, timestamp);
+        update.apply();
     }
 
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetter.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetter.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,22 +50,20 @@
 public class VmLatestPojoListGetter<T extends TimeStampedPojo> {
 
     private final Storage storage;
-    private final Category cat;
-    private final Class<T> resultClass;
+    private final Category<T> cat;
 
-    public VmLatestPojoListGetter(Storage storage, Category cat, Class<T> resultClass) {
+    public VmLatestPojoListGetter(Storage storage, Category<T> cat) {
         this.storage = storage;
         this.cat = cat;
-        this.resultClass = resultClass;
     }
 
     public List<T> getLatest(VmRef vmRef, long since) {
-        Query query = buildQuery(vmRef, since);
+        Query<T> query = buildQuery(vmRef, since);
         return getLatest(query);
     }
 
-    private List<T> getLatest(Query query) {
-        Cursor<T> cursor = storage.findAllPojos(query, resultClass);
+    private List<T> getLatest(Query<T> query) {
+        Cursor<T> cursor = query.execute();
         List<T> result = new ArrayList<>();
         while (cursor.hasNext()) {
             T pojo = cursor.next();
@@ -74,13 +72,12 @@
         return result;
     }
 
-    protected Query buildQuery(VmRef vmRef, long since) {
-        Query query = storage.createQuery()
-                .from(cat)
-                .where(Key.AGENT_ID, Criteria.EQUALS, vmRef.getAgent().getAgentId())
-                .where(Key.VM_ID, Criteria.EQUALS, vmRef.getId())
-                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since)
-                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+    protected Query<T> buildQuery(VmRef vmRef, long since) {
+        Query<T> query = storage.createQuery(cat);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, vmRef.getAgent().getAgentId());
+        query.where(Key.VM_ID, Criteria.EQUALS, vmRef.getId());
+        query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         return query;
     }
 
--- a/common/core/src/main/java/com/redhat/thermostat/test/MockQuery.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +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.test;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import com.redhat.thermostat.storage.core.AbstractQuery;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
-
-public class MockQuery extends AbstractQuery {
-
-    public static class WhereClause <T> {
-        public final Key<T> key;
-        public final Criteria criteria;
-        public final T value;
-
-        public WhereClause(Key<T> key, Criteria criteria, T value) {
-            this.key = key;
-            this.criteria = criteria;
-            this.value = value;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null) {
-                return false;
-            }
-            if (obj == this) {
-                return true;
-            }
-            if (!(obj instanceof WhereClause)) {
-                return false;
-            }
-            WhereClause<?> other = (WhereClause<?>) obj;
-            return Objects.equals(key, other.key) && Objects.equals(criteria, other.criteria) && Objects.equals(value, other.value);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(key, criteria, value);
-        }
-    }
-
-    private final List<WhereClause<?>> whereClauses = new ArrayList<>();
-    private Category category;
-
-    @Override
-    public MockQuery from(Category category) {
-        setCategory(category);
-        return this;
-    }
-
-    public Category getCategory() {
-        return category;
-    }
-
-    public void setCategory(Category category) {
-        this.category = category;
-    }
-
-    @Override
-    public <T> MockQuery where(Key<T> key, Criteria criteria, T value) {
-        whereClauses.add(new WhereClause<>(key, criteria, value));
-        return this;
-    }
-
-    public List<WhereClause<?>> getWhereClauses() {
-        return whereClauses;
-    }
-
-    public int getWhereClausesCount() {
-        return whereClauses.size();
-    }
-
-    public <T> boolean hasWhereClause(Key<T> key, Criteria criteria, T value) {
-        for (WhereClause<?> whereClause: whereClauses) {
-            if (whereClause.equals(new WhereClause<T>(key, criteria, value))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean hasWhereClauseFor(Key<?> key) {
-        for (WhereClause<?> where : whereClauses) {
-            if (where.key.equals(key)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof MockQuery)) {
-            return false;
-        }
-        MockQuery other = (MockQuery) obj;
-        return Objects.equals(getCategory(), other.getCategory()) && Objects.equals(whereClauses, other.whereClauses);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getCategory(), whereClauses);
-    }
-
-    public boolean hasSort(Key<?> key, SortDirection direction) {
-        
-        return getSorts().contains(new Sort(key, direction));
-    }
-
-}
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/AgentInfoDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/AgentInfoDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,7 +40,6 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -56,12 +55,12 @@
 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.storage.core.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.test.MockQuery;
 
 public class AgentInfoDAOTest {
 
@@ -119,9 +118,9 @@
         when(agentCursor.next()).thenReturn(agent1).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.findAllPojos(any(Query.class), same(AgentInformation.class))).thenReturn(agentCursor);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
+        Query query = mock(Query.class);
+        when(query.execute()).thenReturn(agentCursor);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
         AgentInfoDAOImpl dao = new AgentInfoDAOImpl(storage);
 
         List<AgentInformation> allAgentInfo = dao.getAllAgentInformation();
@@ -140,16 +139,18 @@
         when(agentCursor.hasNext()).thenReturn(true).thenReturn(false);
         when(agentCursor.next()).thenReturn(agent1).thenReturn(null);
 
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(query, AgentInformation.class)).thenReturn(agentCursor);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(agentCursor);
 
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
         List<AgentInformation> aliveAgents = dao.getAliveAgents();
 
-        assertEquals(AgentInfoDAO.CATEGORY, query.getCategory());
-        assertTrue(query.hasWhereClause(AgentInfoDAO.ALIVE_KEY, Criteria.EQUALS, true));
+        verify(storage).createQuery(AgentInfoDAO.CATEGORY);
+        verify(query).where(AgentInfoDAO.ALIVE_KEY, Criteria.EQUALS, true);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, aliveAgents.size());
 
@@ -162,9 +163,14 @@
     public void verifyGetAgentInformationWhenStorageCantFindIt() {
         HostRef agentRef = mock(HostRef.class);
 
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(false);
+        when(cursor.next()).thenReturn(null);
+        when(query.execute()).thenReturn(cursor);
+
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(query);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
 
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
 
@@ -179,16 +185,21 @@
         when(agentRef.getAgentId()).thenReturn(agentInfo1.getAgentId());
 
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findPojo(query, AgentInformation.class)).thenReturn(agentInfo1);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(agentInfo1).thenReturn(null);
+        when(query.execute()).thenReturn(cursor);
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
 
         AgentInformation computed = dao.getAgentInformation(agentRef);
 
-        assertEquals(AgentInfoDAO.CATEGORY, query.getCategory());
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, agentInfo1.getAgentId()));
-
+        verify(storage).createQuery(AgentInfoDAO.CATEGORY);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, agentInfo1.getAgentId());
+        verify(query).limit(1);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
         AgentInformation expected = agentInfo1;
         assertSame(expected, computed);
     }
@@ -196,32 +207,36 @@
     @Test
     public void verifyAddAgentInformation() {
         Storage storage = mock(Storage.class);
+        Replace replace = mock(Replace.class);
+        when(storage.createReplace(any(Category.class))).thenReturn(replace);
+
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
 
         dao.addAgentInformation(agentInfo1);
 
-        verify(storage).putPojo(AgentInfoDAO.CATEGORY, true, agentInfo1);
-
+        verify(storage).createReplace(AgentInfoDAO.CATEGORY);
+        verify(replace).setPojo(agentInfo1);
+        verify(replace).apply();
     }
 
     @Test
     public void verifyUpdateAgentInformation() {
 
-        Update mockUpdate = QueryTestHelper.createMockUpdate();
+        Update mockUpdate = mock(Update.class);
         Storage storage = mock(Storage.class);
-        when(storage.createUpdate()).thenReturn(mockUpdate);
+        when(storage.createUpdate(any(Category.class))).thenReturn(mockUpdate);
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
 
         dao.updateAgentInformation(agentInfo1);
 
-        verify(mockUpdate).from(AgentInfoDAO.CATEGORY);
+        verify(storage).createUpdate(AgentInfoDAO.CATEGORY);
         verify(mockUpdate).where(Key.AGENT_ID, "1234");
         verify(mockUpdate).set(AgentInfoDAO.START_TIME_KEY, 100L);
         verify(mockUpdate).set(AgentInfoDAO.STOP_TIME_KEY, 10L);
         verify(mockUpdate).set(AgentInfoDAO.CONFIG_LISTEN_ADDRESS, "foobar:666");
         verify(mockUpdate).set(AgentInfoDAO.ALIVE_KEY, true);
+        verify(mockUpdate).apply();
         verifyNoMoreInteractions(mockUpdate);
-        verify(storage).updatePojo(mockUpdate);
 
     }
 
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/BackendInfoDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/BackendInfoDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -38,9 +38,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
@@ -51,14 +53,15 @@
 import org.junit.Test;
 import org.mockito.InOrder;
 
+import com.redhat.thermostat.storage.core.Add;
 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.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.BackendInformation;
-import com.redhat.thermostat.test.MockQuery;
 
 public class BackendInfoDAOTest {
 
@@ -109,11 +112,16 @@
     @Test
     public void verifyAddBackendInformation() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+
         BackendInfoDAO dao = new BackendInfoDAOImpl(storage);
 
         dao.addBackendInformation(backendInfo1);
 
-        verify(storage).putPojo(BackendInfoDAO.CATEGORY, false, backendInfo1);
+        verify(storage).createAdd(BackendInfoDAO.CATEGORY);
+        verify(add).setPojo(backendInfo1);
+        verify(add).apply();
     }
 
     @Test
@@ -127,17 +135,19 @@
         when(backendCursor.hasNext()).thenReturn(true).thenReturn(false);
         when(backendCursor.next()).thenReturn(backend1).thenReturn(null);
 
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(query, BackendInformation.class)).thenReturn(backendCursor);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(backendCursor);
 
         BackendInfoDAO dao = new BackendInfoDAOImpl(storage);
 
         List<BackendInformation> result = dao.getBackendInformation(agentref);
 
-        assertEquals(BackendInfoDAO.CATEGORY, query.getCategory());
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        verify(storage).createQuery(BackendInfoDAO.CATEGORY);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(Arrays.asList(backendInfo1), result);
     }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,9 +40,8 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -50,9 +49,8 @@
 import java.util.Collection;
 
 import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
@@ -60,7 +58,7 @@
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.storage.model.HostInfo;
-import com.redhat.thermostat.test.MockQuery;
+
 
 public class HostInfoDAOTest {
 
@@ -99,9 +97,13 @@
     public void testGetHostInfo() {
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(new MockQuery());
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
         HostInfo info = new HostInfo(HOST_NAME, OS_NAME, OS_KERNEL, CPU_MODEL, CPU_NUM, MEMORY_TOTAL);
-        when(storage.findPojo(any(Query.class), same(HostInfo.class))).thenReturn(info);
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(info).thenReturn(null);
+        when(query.execute()).thenReturn(cursor);
         AgentInfoDAO agentInfoDao = mock(AgentInfoDAO.class);
 
         HostInfo result = new HostInfoDAOImpl(storage, agentInfoDao).getHostInfo(new HostRef("some uid", HOST_NAME));
@@ -132,13 +134,9 @@
         when(cursor.next()).thenReturn(hostConfig);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
         
         return storage;
     }
@@ -174,13 +172,9 @@
         when(cursor.next()).thenReturn(hostConfig1).thenReturn(hostConfig2).thenReturn(hostConfig3);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
         
         return storage;
     }
@@ -188,13 +182,18 @@
     @Test
     public void testPutHostInfo() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+
         AgentInfoDAO agentInfo = mock(AgentInfoDAO.class);
 
         HostInfo info = new HostInfo(HOST_NAME, OS_NAME, OS_KERNEL, CPU_MODEL, CPU_NUM, MEMORY_TOTAL);
         HostInfoDAO dao = new HostInfoDAOImpl(storage, agentInfo);
         dao.putHostInfo(info);
 
-        verify(storage).putPojo(HostInfoDAO.hostInfoCategory, false, info);
+        verify(storage).createAdd(HostInfoDAO.hostInfoCategory);
+        verify(add).setPojo(info);
+        verify(add).apply();
     }
 
     @Test
@@ -219,7 +218,7 @@
 
         assertEquals(1, hosts.size());
         assertTrue(hosts.contains(new HostRef("123", "fluffhost1")));
-        verify(storage, times(1)).findAllPojos(any(Query.class), same(HostInfo.class));
+        verify(storage).createQuery(HostInfoDAO.hostInfoCategory);
     }
     
     private Pair<Storage, AgentInfoDAO> setupForSingleAliveHost() {
@@ -254,13 +253,10 @@
         // storage
         
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor1);
+        Query query = mock(Query.class);
+        
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor1);
 
         AgentInfoDAO agentDao = mock(AgentInfoDAO.class);
         when(agentDao.getAliveAgents()).thenReturn(Arrays.asList(agentInfo1));
@@ -282,7 +278,7 @@
         assertTrue(hosts.contains(new HostRef("123", "fluffhost1")));
         assertTrue(hosts.contains(new HostRef("456", "fluffhost2")));
         assertTrue(hosts.contains(new HostRef("678", "fluffhost3")));
-        verify(storage, times(3)).findAllPojos(any(Query.class), same(HostInfo.class));
+        verify(storage, atLeast(3)).createQuery(HostInfoDAO.hostInfoCategory);
     }
     
     private Pair<Storage, AgentInfoDAO> setupForAliveHost3() {
@@ -332,15 +328,9 @@
         // storage
         
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(HostInfo.class))).thenReturn(cursor1).
-                                                                           thenReturn(cursor2).
-                                                                           thenReturn(cursor3);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor1).thenReturn(cursor2).thenReturn(cursor3);
         
         AgentInfoDAO agentDao = mock(AgentInfoDAO.class);
         when(agentDao.getAliveAgents()).thenReturn(Arrays.asList(agentInfo1, agentInfo2, agentInfo3));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostLatestPojoListGetterTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,8 +39,11 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.List;
@@ -52,10 +55,10 @@
 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.Storage;
+import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.CpuStat;
-import com.redhat.thermostat.test.MockQuery;
 
 public class HostLatestPojoListGetterTest {
     private static final String AGENT_ID = "agentid";
@@ -63,7 +66,7 @@
     private static final String CATEGORY_NAME = "hostcategory";
     // Make this one static so we don't get IllegalStateException from trying
     // to make category of same name while running tests in same classloader.
-    private static final Category cat =  new Category(CATEGORY_NAME);
+    private static final Category<CpuStat> cat =  new Category<>(CATEGORY_NAME, CpuStat.class);
 
     private static long t1 = 1;
     private static long t2 = 5;
@@ -95,36 +98,38 @@
     @Test
     public void testBuildQuery() {
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when (storage.createQuery()).thenReturn(query);
+        Query query = mock(Query.class);
+        when (storage.createQuery(any(Category.class))).thenReturn(query);
 
-        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, CpuStat.class);
-        query = (MockQuery) getter.buildQuery(ref, 123);
+        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat);
+        query = getter.buildQuery(ref, 123);
 
         assertNotNull(query);
-        assertEquals(cat, query.getCategory());
-        assertEquals(2, query.getWhereClausesCount());
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
+        verify(storage).createQuery(cat);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verifyNoMoreInteractions(query);
     }
 
     @Test
     public void testBuildQueryPopulatesUpdateTimes() {
         Storage storage = mock(Storage.class);
-        MockQuery ignored = new MockQuery();
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(ignored).thenReturn(query);
+        Query ignored = mock(Query.class);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(ignored).thenReturn(query);
 
-        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, CpuStat.class);
-        ignored = (MockQuery) getter.buildQuery(ref,Long.MIN_VALUE); // Ignore first return value.
+        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat);
+        ignored = getter.buildQuery(ref,Long.MIN_VALUE); // Ignore first return value.
 
-        query = (MockQuery) getter.buildQuery(ref, Long.MIN_VALUE);
+        query = getter.buildQuery(ref, Long.MIN_VALUE);
 
         assertNotNull(query);
-        assertEquals(cat, query.getCategory());
-        assertEquals(2, query.getWhereClausesCount());
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(storage, times(2)).createQuery(cat);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verifyNoMoreInteractions(query);
     }
 
     @Test
@@ -135,16 +140,16 @@
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(query, CpuStat.class)).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
-        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat, CpuStat.class);
+        HostLatestPojoListGetter<CpuStat> getter = new HostLatestPojoListGetter<>(storage, cat);
 
         List<CpuStat> stats = getter.getLatest(ref, Long.MIN_VALUE);
 
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
 
         assertNotNull(stats);
         assertEquals(2, stats.size());
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -37,10 +37,11 @@
 package com.redhat.thermostat.common.dao;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
@@ -48,11 +49,14 @@
 
 import org.junit.Test;
 
+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.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.NetworkInterfaceInfo;
-import com.redhat.thermostat.test.MockQuery;
 
 public class NetworkInterfaceInfoDAOTest {
 
@@ -86,9 +90,9 @@
         when(cursor.next()).thenReturn(niInfo);
 
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(query, NetworkInterfaceInfo.class)).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -96,7 +100,9 @@
         NetworkInterfaceInfoDAO dao = new NetworkInterfaceInfoDAOImpl(storage);
         List<NetworkInterfaceInfo> netInfo = dao.getNetworkInterfaces(hostRef);
 
-        assertFalse(query.hasWhereClauseFor(Key.TIMESTAMP));
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "system");
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, netInfo.size());
 
@@ -110,12 +116,17 @@
     @Test
     public void testPutNetworkInterfaceInfo() {
         Storage storage = mock(Storage.class);
+        Replace replace = mock(Replace.class);
+        when(storage.createReplace(any(Category.class))).thenReturn(replace);
+
         NetworkInterfaceInfo info = new NetworkInterfaceInfo(INTERFACE_NAME);
         info.setIp4Addr(IPV4_ADDR);
         info.setIp6Addr(IPV6_ADDR);
         NetworkInterfaceInfoDAO dao = new NetworkInterfaceInfoDAOImpl(storage);
         dao.putNetworkInterfaceInfo(info);
 
-        verify(storage).putPojo(NetworkInterfaceInfoDAO.networkInfoCategory, true, info);
+        verify(storage).createReplace(NetworkInterfaceInfoDAO.networkInfoCategory);
+        verify(replace).setPojo(info);
+        verify(replace).apply();
     }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/QueryTestHelper.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/QueryTestHelper.java	Mon Jan 14 14:04:50 2013 -0500
@@ -43,23 +43,11 @@
 
 import com.redhat.thermostat.storage.core.Category;
 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.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 
 public class QueryTestHelper {
 
     @SuppressWarnings("unchecked")
-    public static Update createMockUpdate() {
-        Update mockUpdate = mock(Update.class);
-        when(mockUpdate.from(any(Category.class))).thenReturn(mockUpdate);
-        when(mockUpdate.where(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockUpdate.set(any(Key.class), any())).thenReturn(mockUpdate);
-        return mockUpdate;
-    }
-
-    @SuppressWarnings("unchecked")
     public static Remove createMockRemove() {
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
@@ -67,11 +55,4 @@
         return mockRemove;
     }
 
-    @SuppressWarnings("unchecked")
-    public static Query createMockQuery() {
-        Query mockQuery = mock(Query.class);
-        when(mockQuery.from(any(Category.class))).thenReturn(mockQuery);
-        when(mockQuery.where(any(Key.class), any(Criteria.class), any())).thenReturn(mockQuery);
-        return mockQuery;
-    }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -57,11 +57,10 @@
 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.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmInfo;
-import com.redhat.thermostat.test.MockQuery;
 
 public class VmInfoDAOTest {
 
@@ -125,10 +124,13 @@
     public void testGetVmInfo() {
 
         Storage storage = mock(Storage.class);
-        Query query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
         VmInfo expected = new VmInfo(vmId, startTime, stopTime, jVersion, jHome, mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs, props, env, libs);
-        when(storage.findPojo(query, VmInfo.class)).thenReturn(expected);
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(expected).thenReturn(null);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -146,9 +148,11 @@
     public void testGetVmInfoUnknownVM() {
 
         Storage storage = mock(Storage.class);
-        Query query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
-
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        Cursor cursor = mock(Cursor.class);
+        when(query.execute()).thenReturn(cursor);
+        
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
 
@@ -178,9 +182,6 @@
     }
 
     private Storage setupStorageForSingleVM() {
-      Query expectedQuery = new MockQuery()
-          .from(VmInfoDAO.vmInfoCategory)
-          .where(Key.AGENT_ID, Criteria.EQUALS, "123");
 
       VmInfo vm1 = new VmInfo();
       vm1.setVmPid(123);
@@ -192,8 +193,9 @@
       when(singleVMCursor.next()).thenReturn(vm1);
 
       Storage storage = mock(Storage.class);
-      when(storage.createQuery()).thenReturn(new MockQuery());
-      when(storage.findAllPojos(expectedQuery, VmInfo.class)).thenReturn(singleVMCursor);
+      Query query = mock(Query.class);
+      when(storage.createQuery(any(Category.class))).thenReturn(query);
+      when(query.execute()).thenReturn(singleVMCursor);
       return storage;
   }
 
@@ -210,9 +212,6 @@
     }
 
     private Storage setupStorageForMultiVM() {
-      Query expectedQuery = new MockQuery()
-          .from(VmInfoDAO.vmInfoCategory)
-          .where(Key.AGENT_ID, Criteria.EQUALS, "456");
 
       VmInfo vm1 = new VmInfo();
       vm1.setVmPid(123);
@@ -228,8 +227,9 @@
       when(multiVMsCursor.next()).thenReturn(vm1).thenReturn(vm2);
 
       Storage storage = mock(Storage.class);
-      when(storage.createQuery()).thenReturn(new MockQuery());
-      when(storage.findAllPojos(expectedQuery, VmInfo.class)).thenReturn(multiVMsCursor);
+      Query query = mock(Query.class);
+      when(storage.createQuery(any(Category.class))).thenReturn(query);
+      when(query.execute()).thenReturn(multiVMsCursor);
       return storage;
   }
 
@@ -253,27 +253,33 @@
     public void testPutVmInfo() {
 
         Storage storage = mock(Storage.class);
+        Replace replace = mock(Replace.class);
+        when(storage.createReplace(any(Category.class))).thenReturn(replace);
+
         VmInfo info = new VmInfo(vmId, startTime, stopTime, jVersion, jHome,
                 mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs,
                 props, env, libs);
         VmInfoDAO dao = new VmInfoDAOImpl(storage);
         dao.putVmInfo(info);
 
-        verify(storage).putPojo(VmInfoDAO.vmInfoCategory, true, info);
+        verify(storage).createReplace(VmInfoDAO.vmInfoCategory);
+        verify(replace).setPojo(info);
+        verify(replace).apply();
     }
 
     @Test
     public void testPutVmStoppedTime() {
-        Update mockUpdate = QueryTestHelper.createMockUpdate();
+        Update mockUpdate = mock(Update.class);
         Storage storage = mock(Storage.class);
-        when(storage.createUpdate()).thenReturn(mockUpdate);
+        when(storage.createUpdate(any(Category.class))).thenReturn(mockUpdate);
+
         VmInfoDAO dao = new VmInfoDAOImpl(storage);
         dao.putVmStoppedTime(vmId, stopTime);
 
-        verify(mockUpdate).from(VmInfoDAO.vmInfoCategory);
+        verify(storage).createUpdate(VmInfoDAO.vmInfoCategory);
         verify(mockUpdate).where(Key.VM_ID, 1);
         verify(mockUpdate).set(VmInfoDAO.stopTimeKey, 3L);
+        verify(mockUpdate).apply();
         verifyNoMoreInteractions(mockUpdate);
-        verify(storage).updatePojo(mockUpdate);
     }
 }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmLatestPojoListGetterTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -38,11 +38,11 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.List;
@@ -54,10 +54,9 @@
 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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmClassStat;
-import com.redhat.thermostat.test.MockQuery;
 
 public class VmLatestPojoListGetterTest {
     private static final String AGENT_ID = "agentid";
@@ -67,7 +66,7 @@
     private static final String CATEGORY_NAME = "vmcategory";
     // Make this one static so we don't get IllegalStateException from trying
     // to make category of same name while running tests in same classloader.
-    private static final Category cat =  new Category(CATEGORY_NAME);
+    private static final Category<VmClassStat> cat =  new Category<>(CATEGORY_NAME, VmClassStat.class);
 
     private static long t1 = 1;
     private static long t2 = 5;
@@ -93,37 +92,39 @@
     @Test
     public void testBuildQuery() {
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
 
-        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, VmClassStat.class);
-        query = (MockQuery) getter.buildQuery(vmRef, 123l);
+        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat);
+        query = getter.buildQuery(vmRef, 123l);
 
         assertNotNull(query);
-        assertEquals(cat, query.getCategory());
-        assertEquals(3, query.getWhereClausesCount());
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
-        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_PID));
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
+        verify(storage).createQuery(cat);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, VM_PID);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verifyNoMoreInteractions(query);
     }
 
     @Test
     public void testBuildQueryPopulatesUpdateTimes() {
         Storage storage = mock(Storage.class);
-        MockQuery ignored = new MockQuery();
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(ignored).thenReturn(query);
+        Query ignored = mock(Query.class);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(ignored).thenReturn(query);
 
-        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, VmClassStat.class);
+        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat);
         getter.buildQuery(vmRef, Long.MIN_VALUE); // Ignore first return value.
-        query = (MockQuery) getter.buildQuery(vmRef, Long.MIN_VALUE);
+        query = getter.buildQuery(vmRef, Long.MIN_VALUE);
 
         assertNotNull(query);
-        assertEquals(cat, query.getCategory());
-        assertEquals(3, query.getWhereClausesCount());
-        assertTrue(query.hasWhereClauseFor(Key.AGENT_ID));
-        assertTrue(query.hasWhereClauseFor(Key.VM_ID));
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(storage, times(2)).createQuery(cat);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, VM_PID);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verifyNoMoreInteractions(query);
     }
 
     @Test
@@ -134,19 +135,21 @@
         when(cursor.next()).thenReturn(result1).thenReturn(result2).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(any(Query.class), same(VmClassStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
-        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat, VmClassStat.class);
+        VmLatestPojoListGetter<VmClassStat> getter = new VmLatestPojoListGetter<>(storage, cat);
 
         List<VmClassStat> stats = getter.getLatest(vmRef, t2);
 
-        verify(storage).findAllPojos(any(Query.class), same(VmClassStat.class));
-
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
-        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_PID));
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, t2));
+        verify(storage).createQuery(cat);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, VM_PID);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, t2);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertNotNull(stats);
         assertEquals(2, stats.size());
--- a/distribution/config/db.properties	Wed Jan 09 14:59:30 2013 -0500
+++ b/distribution/config/db.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -1,2 +1,57 @@
+# 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.
+
+###################################################################
+# Basic storage configuration
+###################################################################
+
 PORT=27518
 BIND=127.0.0.1
+
+###################################################################
+# SSL configuration
+###################################################################
+# Uncomment the following line in order to start storage (currently
+# mongodb) with SSL enabled.
+#SSL_ENABLE=true
+#
+# If SSL_ENABLE=true, the path to the PEM encoded private key and
+# the server certificate needs to be specified. Uncomment the
+# following line and set the path appropriately. 
+#SSL_PEM_FILE=/path/to/some/pem/file.pem
+#
+# If SSL_ENABLE=true, the passphrase for the server certificate
+# needs to be specified. If the server key was not encrypted any
+# non-empty password will work.
+#SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- a/distribution/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ b/distribution/pom.xml	Mon Jan 14 14:04:50 2013 -0500
@@ -322,11 +322,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-launcher</artifactId>
       <version>${project.version}</version>
     </dependency>
--- a/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/CpuStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/CpuStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -48,7 +48,7 @@
 
     static Key<List<Double>> cpuLoadKey = new Key<>("perProcessorUsage", false);
 
-    static final Category cpuStatCategory = new Category("cpu-stats",
+    static final Category<CpuStat> cpuStatCategory = new Category<>("cpu-stats", CpuStat.class,
             Key.AGENT_ID, Key.TIMESTAMP, cpuLoadKey);
 
     List<CpuStat> getLatestCpuStats(HostRef ref, long since);
--- a/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-cpu/common/src/main/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.common.dao.HostLatestPojoListGetter;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.CpuStat;
 
@@ -53,7 +54,7 @@
     CpuStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(cpuStatCategory);
-        this.getter = new HostLatestPojoListGetter<>(storage, cpuStatCategory, CpuStat.class);
+        this.getter = new HostLatestPojoListGetter<>(storage, cpuStatCategory);
     }
 
     @Override
@@ -63,7 +64,9 @@
 
     @Override
     public void putCpuStat(CpuStat stat) {
-        storage.putPojo(cpuStatCategory, false, stat);
+        Put add = storage.createAdd(cpuStatCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 
     @Override
--- a/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-cpu/common/src/test/java/com/redhat/thermostat/host/cpu/common/internal/CpuStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,9 +41,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
@@ -53,15 +55,14 @@
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
-import com.redhat.thermostat.host.cpu.common.internal.CpuStatDAOImpl;
+import com.redhat.thermostat.storage.core.Add;
 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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.CpuStat;
-import com.redhat.thermostat.test.MockQuery;
 
 public class CpuStatDAOTest {
 
@@ -82,7 +83,7 @@
         @SuppressWarnings("unchecked")
         Cursor<CpuStat> cursor = mock(Cursor.class);
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
         HostRef hostRef = mock(HostRef.class);
         CpuStatDAO dao = new CpuStatDAOImpl(storage);
 
@@ -92,13 +93,17 @@
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(cpuStat);
 
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(query, CpuStat.class)).thenReturn(cursor);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
         when(hostRef.getAgentId()).thenReturn("system");
 
         List<CpuStat> cpuStats = dao.getLatestCpuStats(hostRef, Long.MIN_VALUE);
 
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "system");
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, cpuStats.size());
         CpuStat stat = cpuStats.get(0);
@@ -113,7 +118,7 @@
         @SuppressWarnings("unchecked")
         Cursor<CpuStat> cursor = mock(Cursor.class);
         Storage storage = mock(Storage.class);
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
         HostRef hostRef = mock(HostRef.class);
 
         CpuStatDAO dao = new CpuStatDAOImpl(storage);
@@ -123,26 +128,31 @@
         when(cursor.hasNext()).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(cpuStat);
 
-        when(storage.createQuery()).thenReturn(query);
-        when(storage.findAllPojos(any(Query.class), same(CpuStat.class))).thenReturn(cursor);
+        when(storage.createQuery(CpuStatDAO.cpuStatCategory)).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
         when(hostRef.getAgentId()).thenReturn("system");
 
         dao.getLatestCpuStats(hostRef, Long.MIN_VALUE);
         dao.getLatestCpuStats(hostRef, Long.MIN_VALUE);
 
-        verify(storage, times(2)).findAllPojos(query, CpuStat.class);
-
-        query.hasWhereClauseFor(Key.TIMESTAMP);
+        verify(query, times(2)).execute();
+        verify(query, atLeastOnce()).where(same(Key.AGENT_ID), same(Criteria.EQUALS), any());
+        verify(query, atLeastOnce()).where(same(Key.TIMESTAMP), any(Criteria.class), any());
     }
 
     @Test
     public void testPutCpuStat() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+        
         CpuStat stat = new CpuStat(1,  new double[] {5.0, 10.0, 15.0});
         CpuStatDAO dao = new CpuStatDAOImpl(storage);
         dao.putCpuStat(stat);
 
-        verify(storage).putPojo(CpuStatDAO.cpuStatCategory, false, stat);
+        verify(storage).createAdd(CpuStatDAO.cpuStatCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
     }
 
     @Test
--- a/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/MemoryStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/MemoryStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -54,7 +54,7 @@
     static Key<Long> memorySwapFreeKey = new Key<>("swapFree", false);
     static Key<Long> memoryCommitLimitKey = new Key<>("commitLimit", false);
 
-    static final Category memoryStatCategory = new Category("memory-stats",
+    static final Category<MemoryStat> memoryStatCategory = new Category<>("memory-stats", MemoryStat.class,
             Key.AGENT_ID, Key.TIMESTAMP, memoryTotalKey, memoryFreeKey, memoryBuffersKey,
             memoryCachedKey, memorySwapTotalKey, memorySwapFreeKey, memoryCommitLimitKey);
 
--- a/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-memory/common/src/main/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.common.dao.HostLatestPojoListGetter;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.host.memory.common.MemoryStatDAO;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.MemoryStat;
 
@@ -53,7 +54,7 @@
     MemoryStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(memoryStatCategory);
-        this.getter = new HostLatestPojoListGetter<>(storage, memoryStatCategory, MemoryStat.class);
+        this.getter = new HostLatestPojoListGetter<>(storage, memoryStatCategory);
     }
 
     @Override
@@ -63,7 +64,9 @@
 
     @Override
     public void putMemoryStat(MemoryStat stat) {
-        storage.putPojo(memoryStatCategory, false, stat);
+        Put add = storage.createAdd(memoryStatCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 
     @Override
--- a/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/host-memory/common/src/test/java/com/redhat/thermostat/host/memory/common/internal/MemoryStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,6 +42,7 @@
 import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
@@ -54,6 +55,7 @@
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.host.memory.common.MemoryStatDAO;
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
@@ -61,7 +63,6 @@
 import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.MemoryStat;
-import com.redhat.thermostat.test.MockQuery;
 
 public class MemoryStatDAOTest {
 
@@ -101,13 +102,9 @@
         when(cursor.next()).thenReturn(memStat1);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(MemoryStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -115,9 +112,12 @@
         MemoryStatDAO dao = new MemoryStatDAOImpl(storage);
         List<MemoryStat> memoryStats = dao.getLatestMemoryStats(hostRef, Long.MIN_VALUE);
 
-        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
-        verify(storage).findAllPojos(arg.capture(), same(MemoryStat.class));
-        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(storage).createQuery(MemoryStatDAO.memoryStatCategory);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "system");
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, memoryStats.size());
         MemoryStat stat = memoryStats.get(0);
@@ -135,11 +135,16 @@
     @Test
     public void testPutMemoryStat() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+
         MemoryStat stat = new MemoryStat(TIMESTAMP, TOTAL, FREE, BUFFERS, CACHED, SWAP_TOTAL, SWAP_FREE, COMMIT_LIMIT);
         MemoryStatDAO dao = new MemoryStatDAOImpl(storage);
         dao.putMemoryStat(stat);
 
-        verify(storage).putPojo(MemoryStatDAO.memoryStatCategory, false, stat);
+        verify(storage).createAdd(MemoryStatDAO.memoryStatCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
     }
 
     @Test
--- a/launcher/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ b/launcher/pom.xml	Mon Jan 14 14:04:50 2013 -0500
@@ -110,11 +110,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common-test</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/BundleManager.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+
+import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+
+/**
+ * A Service that provides features to load bundles for given command names.
+ */
+public abstract class BundleManager {
+
+    public abstract void setPrintOSGiInfo(boolean printOSGiInfo);
+
+    public abstract void setCommandInfoSource(CommandInfoSource source);
+
+    public abstract void addBundlesFor(String commandName) throws BundleException, CommandInfoNotFoundException, IOException;
+
+    public static void preLoadBundles(Framework framework, List<String> bundleLocations,
+            boolean printOSGiInfo) throws BundleException {
+        BundleLoader loader = new BundleLoader(printOSGiInfo);
+        loader.installAndStartBundles(framework, bundleLocations);
+    }
+
+    public abstract Configuration getConfiguration();
+
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/Activator.java	Mon Jan 14 14:04:50 2013 -0500
@@ -36,70 +36,85 @@
 
 package com.redhat.thermostat.launcher.internal;
 
-import java.util.Map;
-
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.CommandLoadingBundleActivator;
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.Configuration;
 import com.redhat.thermostat.common.cli.CommandContextFactory;
 import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 public class Activator extends CommandLoadingBundleActivator {
+    
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    class RegisterLauncherCustomizer implements ServiceTrackerCustomizer {
 
-    class RegisterLauncherAction implements Action {
-
+        private ServiceRegistration launcherReg;
+        private ServiceRegistration bundleManReg;
+        private ServiceRegistration cmdInfoReg;
         private BundleContext context;
-        private ServiceReference registryReference;
+        private BundleManager bundleService;
 
-        RegisterLauncherAction(BundleContext context) {
+        RegisterLauncherCustomizer(BundleContext context, BundleManager bundleService) {
             this.context = context;
+            this.bundleService = bundleService;
         }
 
         @Override
-        public void dependenciesAvailable(Map<String, Object> services) {
-            
-            registryReference = context.getServiceReference(OSGiRegistry.class);
-            OSGiRegistry bundleService = (OSGiRegistry) context.getService(registryReference);
+        public Object addingService(ServiceReference reference) {
+            // keyring is now ready
+            Keyring keyring = (Keyring)context.getService(reference);
+            // Register Launcher service since FrameworkProvider is waiting for it blockingly.
             CommandInfoSourceImpl commands = new CommandInfoSourceImpl(bundleService.getConfiguration().getThermostatHome());
-            context.registerService(CommandInfoSource.class, commands, null);
+            cmdInfoReg = context.registerService(CommandInfoSource.class, commands, null);
             bundleService.setCommandInfoSource(commands);
             LauncherImpl launcher = new LauncherImpl(context,
                     new CommandContextFactory(context), bundleService);
-            launcherServiceRegistration = context.registerService(Launcher.class.getName(), launcher, null);
+            launcherReg = context.registerService(Launcher.class.getName(), launcher, null);
+            bundleManReg = context.registerService(BundleManager.class, bundleService, null);
+            return keyring;
         }
 
         @Override
-        public void dependenciesUnavailable() {
-            launcherServiceRegistration.unregister();
-            context.ungetService(registryReference);
+        public void modifiedService(ServiceReference reference, Object service) {
+            // nothing
+        }
+
+        @Override
+        public void removedService(ServiceReference reference, Object service) {
+            // Keyring is gone, remove launcher, et. al. as well
+            launcherReg.unregister();
+            bundleManReg.unregister();
+            cmdInfoReg.unregister();
         }
 
     }
 
     @SuppressWarnings("rawtypes")
-    private ServiceRegistration launcherServiceRegistration;
-    private MultipleServiceTracker tracker;
+    private ServiceTracker serviceTracker;
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     @Override
     public void start(final BundleContext context) throws Exception {
         super.start(context);
-
-        tracker = new MultipleServiceTracker(context, new Class[] {OSGiRegistry.class, Keyring.class}, new RegisterLauncherAction(context));
-        tracker.open();
+        BundleManager bundleService = new BundleManagerImpl(new Configuration());
+        ServiceTrackerCustomizer customizer = new RegisterLauncherCustomizer(context, bundleService);
+        serviceTracker = new ServiceTracker(context, Keyring.class, customizer);
+        // Track for Keyring service.
+        serviceTracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
         super.stop(context);
-        if (tracker != null) {
-            tracker.close();
+        if (serviceTracker != null) {
+            serviceTracker.close();
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleLoader.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+public class BundleLoader {
+
+    private boolean printOSGiInfo = false;
+
+    BundleLoader() {
+        this(false);
+    }
+
+    public BundleLoader(boolean printOSGiInfo) {
+        setPrintOSGiInfo(printOSGiInfo);
+    }
+
+    public void setPrintOSGiInfo(boolean printOSGiInfo) {
+        this.printOSGiInfo = printOSGiInfo;
+    }
+
+    public List<Bundle> installAndStartBundles(Framework framework,
+            List<String>bundleLocations) throws BundleException {
+        List<Bundle> bundles = new ArrayList<>();
+        BundleContext ctx = framework.getBundleContext();
+        for (String location : bundleLocations) {
+            Bundle bundle = ctx.installBundle(location);
+            if (printOSGiInfo) {
+                System.out.println("BundleLoader: installed bundle: \"" + 
+                        location + "\" as id " + bundle.getBundleId());
+            }
+            bundles.add(bundle);
+        }
+        startBundles(bundles);
+        return bundles;
+    }
+
+    private void startBundles(List<Bundle> bundles) throws BundleException {
+        for (Bundle bundle : bundles) {
+
+            if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
+                if (printOSGiInfo) {
+                    System.out.println("BundleLoader: bundle \"" + bundle.getBundleId() + "\" is a fragment; not starting it");
+                }
+                continue;
+            }
+
+            if (printOSGiInfo) {
+                System.out.println("BundleLoader: starting bundle: \"" + bundle.getBundleId() + "\"");
+            }
+            // We don't want for the framework to set the auto-start bit. Thus, passing
+            // START_TRANSIENT explicitly
+            bundle.start(Bundle.START_TRANSIENT);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/BundleManagerImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.ConfigurationException;
+import com.redhat.thermostat.common.cli.CommandInfoNotFoundException;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.BundleManager;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+
+public class BundleManagerImpl extends BundleManager {
+
+    private CommandInfoSource commandInfos;
+    private Map<String, Bundle> loaded;
+    private Configuration configuration;
+    private BundleLoader loader;
+
+    BundleManagerImpl(Configuration configuration) throws ConfigurationException, FileNotFoundException, IOException {
+        initLoadedBundles();
+        this.configuration = configuration;
+        loader = new BundleLoader(configuration.getPrintOSGiInfo());
+    }
+
+    private void initLoadedBundles() {
+        loaded = new HashMap<>();
+        Framework framework = getFramework(this.getClass());
+        for (Bundle bundle: framework.getBundleContext().getBundles()) {
+            loaded.put(bundle.getLocation(), bundle);
+        }
+    }
+
+    @Override
+    public void setPrintOSGiInfo(boolean printOSGiInfo) {
+        configuration.setPrintOSGiInfo(printOSGiInfo);
+        loader.setPrintOSGiInfo(printOSGiInfo);
+    }
+
+    @Override
+    public void setCommandInfoSource(CommandInfoSource source) {
+        this.commandInfos = source;
+    }
+
+    @Override
+    public void addBundlesFor(String commandName) throws BundleException, IOException, CommandInfoNotFoundException {
+        if (configuration.getPrintOSGiInfo()) {
+            System.out.println("Loading additional bundles for: " + commandName);
+        }
+        List<String> requiredBundles = commandInfos.getCommandInfo(commandName).getDependencyResourceNames();
+        List<String> bundlesToLoad = new ArrayList<>();
+        if (requiredBundles != null) {
+            for (String resource : requiredBundles) {
+                if (!isBundleActive(resource)) {
+                    bundlesToLoad.add(resource);
+                }
+            }
+        }
+        Framework framework = getFramework(this.getClass());
+        List<Bundle> successBundles = loader.installAndStartBundles(framework, bundlesToLoad);
+        for (Bundle bundle : successBundles) {
+            loaded.put(bundle.getLocation(), bundle);
+        }
+    }
+
+    private boolean isBundleActive(String location) {
+        Bundle bundle = loaded.get(location);
+        return (bundle != null) && (bundle.getState() == Bundle.ACTIVE);
+    }
+
+    private Framework getFramework(Class<?> cls) {
+        return (Framework) FrameworkUtil.getBundle(cls).getBundleContext().getBundle(0);
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+}
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/LauncherImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,7 +50,6 @@
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.ApplicationService;
@@ -72,6 +71,7 @@
 import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.launcher.CommonCommandOptions;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.storage.core.ConnectionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
@@ -89,14 +89,14 @@
     private final Semaphore argsBarrier = new Semaphore(0);
 
     private BundleContext context;
-    private OSGiRegistry registry;
+    private BundleManager registry;
     private final DbServiceFactory dbServiceFactory;
     
-    public LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, OSGiRegistry registry) {
+    public LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, BundleManager registry) {
         this(context, cmdCtxFactory, registry, new LoggingInitializer(), new DbServiceFactory());
     }
 
-    LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, OSGiRegistry registry,
+    LauncherImpl(BundleContext context, CommandContextFactory cmdCtxFactory, BundleManager registry,
             LoggingInitializer loggingInitializer, DbServiceFactory dbServiceFactory) {
         this.context = context;
         this.cmdCtxFactory = cmdCtxFactory;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/BundleManagerTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.launch.Framework;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(BundleManager.class)
+public class BundleManagerTest {
+
+    @Test
+    public void testPreLoadBundles() throws Exception {
+        Framework framework = mock(Framework.class);
+        ArrayList<String> bundleLocations = new ArrayList<>();
+        BundleLoader loader = mock(BundleLoader.class);
+        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
+                withArguments(any()).thenReturn(loader);
+
+        BundleManager.preLoadBundles(framework, bundleLocations, true);
+        verify(loader).installAndStartBundles(framework, bundleLocations);
+    }
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ActivatorTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.launcher.internal;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
@@ -46,6 +49,9 @@
 import static org.powermock.api.mockito.PowerMockito.verifyNew;
 import static org.powermock.api.mockito.PowerMockito.whenNew;
 
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
@@ -55,41 +61,55 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.Configuration;
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.common.cli.CommandInfo;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
 import com.redhat.thermostat.common.utils.ServiceRegistry;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.launcher.internal.Activator.RegisterLauncherCustomizer;
+import com.redhat.thermostat.test.StubBundleContext;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({Activator.class})
+@PrepareForTest({Activator.class, Activator.RegisterLauncherCustomizer.class, FrameworkUtil.class})
 public class ActivatorTest {
 
     private BundleContext context;
     private MultipleServiceTracker tracker;
     private ServiceReference registryServiceReference, helpCommandReference;
     private ServiceRegistration launcherServiceRegistration, helpCommandRegistration;
-    private OSGiRegistry registryService;
+    private BundleManager registryService;
     private Command helpCommand;
 
     @Before
     public void setUp() throws Exception {
+        Path tempDir = createStubThermostatHome();
+        System.setProperty("THERMOSTAT_HOME", tempDir.toString());
+        
         context = mock(BundleContext.class);
+        setupOsgiRegistryImplMock();
 
         registryServiceReference = mock(ServiceReference.class);
         launcherServiceRegistration = mock(ServiceRegistration.class);
-        registryService = mock(OSGiRegistry.class);
-        when(context.getServiceReference(eq(OSGiRegistry.class))).thenReturn(registryServiceReference);
+        registryService = mock(BundleManager.class);
+        when(context.getServiceReference(eq(BundleManager.class))).thenReturn(registryServiceReference);
         when(context.getService(eq(registryServiceReference))).thenReturn(registryService);
         when(context.registerService(eq(Launcher.class.getName()), any(), (Dictionary) isNull())).
                 thenReturn(launcherServiceRegistration);
@@ -116,32 +136,95 @@
         tracker = mock(MultipleServiceTracker.class);
         whenNew(MultipleServiceTracker.class).
                 withParameterTypes(BundleContext.class, Class[].class, Action.class).
-                withArguments(eq(context), eq(new Class[] {OSGiRegistry.class, Keyring.class}),
+                withArguments(eq(context), eq(new Class[] {BundleManager.class, Keyring.class}),
                         isA(Action.class)).thenReturn(tracker);
     }
 
     @Test
     public void testActivatorLifecycle() throws Exception {
+        ArgumentCaptor<RegisterLauncherCustomizer> customizerCaptor = ArgumentCaptor.forClass(RegisterLauncherCustomizer.class);
+        ServiceTracker mockTracker = mock(ServiceTracker.class);
+        whenNew(ServiceTracker.class).withParameterTypes(BundleContext.class, Class.class, ServiceTrackerCustomizer.class).withArguments(eq(context),
+                any(Keyring.class), customizerCaptor.capture()).thenReturn(mockTracker);
+        
         Activator activator = new Activator();
-
         activator.start(context);
 
         Hashtable<String, Object> props = new Hashtable<>();
         props.put(ServiceRegistry.SERVICE_NAME, "help");
         verify(context).registerService(eq(Command.class.getName()), isA(HelpCommand.class), eq(props));
 
-        ArgumentCaptor<Action> actionCaptor = ArgumentCaptor.forClass(Action.class);
-        verifyNew(MultipleServiceTracker.class).withArguments(eq(context),
-                eq(new Class[] {OSGiRegistry.class, Keyring.class}),
-                actionCaptor.capture());
-        Action action = actionCaptor.getValue();
+        verify(mockTracker).open();
+        
+        RegisterLauncherCustomizer customizer = customizerCaptor.getValue();
+        assertNotNull(customizer);
+        activator.stop(context);
+        verify(mockTracker).close();
+    }
+    
+    @Test
+    public void testServiceTrackerCustomizer() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        ArgumentCaptor<RegisterLauncherCustomizer> customizerCaptor = ArgumentCaptor.forClass(RegisterLauncherCustomizer.class);
+        ServiceTracker mockTracker = mock(ServiceTracker.class);
+        whenNew(ServiceTracker.class).withParameterTypes(BundleContext.class, Class.class, ServiceTrackerCustomizer.class).withArguments(eq(context),
+                any(Keyring.class), customizerCaptor.capture()).thenReturn(mockTracker);
+        
+        Activator activator = new Activator();
+        context.registerService(Keyring.class, mock(Keyring.class), null);
+        activator.start(context);
+        
+        assertTrue(context.isServiceRegistered(Command.class.getName(), HelpCommand.class));
+        
+        RegisterLauncherCustomizer customizer = customizerCaptor.getValue();
+        assertNotNull(customizer);
+        Keyring keyringService = mock(Keyring.class);
+        context.registerService(Keyring.class, keyringService, null);
+        ServiceReference ref = context.getServiceReference(Keyring.class);
+        customizer.addingService(ref);
+        
+        assertTrue(context.isServiceRegistered(CommandInfoSource.class.getName(), mock(CommandInfoSourceImpl.class).getClass()));
+        assertTrue(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class));
+        assertTrue(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class));
 
-        action.dependenciesAvailable(isA(Map.class));
-        verify(context).registerService(eq(Launcher.class.getName()), isA(Launcher.class), (Dictionary) isNull());
+        customizer.removedService(null, null);
+        
+        assertFalse(context.isServiceRegistered(CommandInfoSource.class.getName(), CommandInfoSourceImpl.class));
+        assertFalse(context.isServiceRegistered(BundleManager.class.getName(), BundleManagerImpl.class));
+        assertFalse(context.isServiceRegistered(Launcher.class.getName(), LauncherImpl.class));
+    }
+    
+    private Path createStubThermostatHome() throws Exception {
+        Path tempDir = Files.createTempDirectory("test");
+        tempDir.toFile().deleteOnExit();
+        System.setProperty("THERMOSTAT_HOME", tempDir.toString());
+        
+        File tempEtc = new File(tempDir.toFile(), "etc");
+        tempEtc.mkdirs();
+        tempEtc.deleteOnExit();
+        
+        File tempProps = new File(tempEtc, "osgi-export.properties");
+        tempProps.createNewFile();
+        tempProps.deleteOnExit();
 
-        activator.stop(context);
-        // osgi will take care of unregistration on bundle stop
-        // verify(launcherServiceRegistration).unregister();
-        verify(tracker).close();
+        File tempBundleProps = new File(tempEtc, "bundles.properties");
+        tempBundleProps.createNewFile();
+        tempBundleProps.deleteOnExit();
+        
+        File tempLibs = new File(tempDir.toFile(), "libs");
+        tempLibs.mkdirs();
+        tempLibs.deleteOnExit();
+        return tempDir;
+    }
+
+    private void setupOsgiRegistryImplMock() {
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        Bundle mockBundle = mock(Bundle.class);
+        when(FrameworkUtil.getBundle(BundleManagerImpl.class)).thenReturn(mockBundle);
+        when(mockBundle.getBundleContext()).thenReturn(context);
+        Bundle mockFramework = mock(Framework.class);
+        when(context.getBundle(0)).thenReturn(mockFramework);
+        when(mockFramework.getBundleContext()).thenReturn(context);
+        when(context.getBundles()).thenReturn(new Bundle[0]);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleLoaderTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+import com.redhat.thermostat.test.Bug;
+
+public class BundleLoaderTest {
+
+    @Test
+    public void verifyBundlesAreInstalledAndStarted() throws BundleException {
+        final String BUNDLE_LOCATION = "bundle-location-1";
+
+        Bundle bundle = mock(Bundle.class);
+        when(bundle.getHeaders()).thenReturn(new Hashtable<String, String>());
+        BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
+        Framework framework = mock(Framework.class);
+        when(framework.getBundleContext()).thenReturn(bundleContext);
+        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
+
+        BundleLoader loader = new BundleLoader();
+        loader.installAndStartBundles(framework, bundleLocations);
+
+        verify(bundle).start(Bundle.START_TRANSIENT);
+    }
+
+    @Bug(id="1227",
+         summary="Make sure launcher does not start fragments",
+         url="http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=1227")
+    @Test
+    public void verifyFragmentsAreInstalledButNotStarted() throws BundleException {
+        final String BUNDLE_LOCATION = "bundle-location-1";
+
+        Bundle bundle = mock(Bundle.class);
+        Dictionary<String, String> bundleHeaders = new Hashtable<>();
+        bundleHeaders.put(Constants.FRAGMENT_HOST, "foo-bar");
+        when(bundle.getHeaders()).thenReturn(bundleHeaders);
+
+        BundleContext bundleContext = mock(BundleContext.class);
+        when(bundleContext.installBundle(BUNDLE_LOCATION)).thenReturn(bundle);
+        Framework framework = mock(Framework.class);
+        when(framework.getBundleContext()).thenReturn(bundleContext);
+        List<String> bundleLocations = Arrays.asList(BUNDLE_LOCATION);
+
+        BundleLoader loader = new BundleLoader();
+        loader.installAndStartBundles(framework, bundleLocations);
+
+        verify(bundle, times(0)).start(Bundle.START_TRANSIENT);
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/BundleManagerImplTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.launcher.internal;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.common.cli.CommandInfo;
+import com.redhat.thermostat.common.cli.CommandInfoSource;
+import com.redhat.thermostat.launcher.internal.BundleLoader;
+import com.redhat.thermostat.launcher.internal.BundleManagerImpl;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({BundleManagerImpl.class, FrameworkUtil.class})
+public class BundleManagerImplTest {
+
+    private static final String cmdName = "one";
+
+    private static final String jar1Name = "/one.jar";
+    private static final String jar2Name = "/two.jar";
+    private static final String jar3Name = "/three.jar";
+
+    private Bundle b1, b2, b3;
+    private List<String> bundleLocs;
+
+    private BundleLoader loader;
+    private Configuration conf;
+    
+    @Before
+    public void setUp() throws Exception {
+        conf = mock(Configuration.class);
+        when(conf.getThermostatHome()).thenReturn("no_matter");
+        bundleLocs = Arrays.asList(jar1Name, jar2Name, jar3Name);
+        b1 = mock(Bundle.class);
+        when(b1.getLocation()).thenReturn(jar1Name);
+        when(b1.getState()).thenReturn(Bundle.ACTIVE);
+        b2 = mock(Bundle.class);
+        when(b2.getLocation()).thenReturn(jar2Name);
+        when(b2.getState()).thenReturn(Bundle.ACTIVE);
+        b3 = mock(Bundle.class);
+        when(b3.getLocation()).thenReturn(jar3Name);
+        when(b3.getState()).thenReturn(Bundle.ACTIVE);
+        List<Bundle> installed = Arrays.asList(b1, b2, b3);
+
+        loader = mock(BundleLoader.class);
+        when(loader.installAndStartBundles(any(Framework.class), eq(bundleLocs))).
+                thenReturn(installed);
+        whenNew(BundleLoader.class).withParameterTypes(Boolean.TYPE).
+                withArguments(any()).thenReturn(loader);
+    }
+
+    @Test
+    public void testLoadBundlesFor() throws Exception {
+        verifyBundlesLoaded(new Bundle[] {}, bundleLocs);
+    }
+
+    @Test
+    public void verifyAlreadyLoadedBundlesNotReloaded() throws Exception {
+        verifyBundlesLoaded(new Bundle[] {b1, b2}, Arrays.asList(jar3Name));
+    }
+
+    private void verifyBundlesLoaded(Bundle[] preloaded, List<String> locationsNeeded) throws Exception {
+        Bundle theBundle = b2;
+        BundleContext theContext = mock(BundleContext.class);
+        when(theContext.getBundles()).thenReturn(preloaded);
+        Framework theFramework = mock(Framework.class);
+        when(theFramework.getBundleContext()).thenReturn(theContext);
+        when(theContext.getBundle(0)).thenReturn(theFramework);
+        when(theBundle.getBundleContext()).thenReturn(theContext);
+        mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
+
+        BundleManagerImpl registry = new BundleManagerImpl(conf);
+        CommandInfoSource infos = mock(CommandInfoSource.class);
+        CommandInfo info = mock(CommandInfo.class);
+        when (info.getDependencyResourceNames()).thenReturn(bundleLocs);
+        when (infos.getCommandInfo(cmdName)).thenReturn(info);
+        registry.setCommandInfoSource(infos);
+        registry.addBundlesFor(cmdName);
+        verify(loader).installAndStartBundles(any(Framework.class), eq(locationsNeeded));
+    }
+
+    @Test
+    public void verifySetOSGiVerbosityByReflection() throws Exception {
+
+        // All this fluff is just so constructor doesn't NPE.
+        Bundle theBundle = b2;
+        BundleContext theContext = mock(BundleContext.class);
+        when(theContext.getBundles()).thenReturn(new Bundle[]{});
+        Framework theFramework = mock(Framework.class);
+        when(theFramework.getBundleContext()).thenReturn(theContext);
+        when(theContext.getBundle(0)).thenReturn(theFramework);
+        when(theBundle.getBundleContext()).thenReturn(theContext);
+        mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(any(Class.class))).thenReturn(theBundle);
+
+        Object registry = new BundleManagerImpl(conf);
+        Class clazz = registry.getClass();
+        Method m = clazz.getMethod("setPrintOSGiInfo", Boolean.TYPE);
+        m.invoke(registry, true); // If this fails, then API has changed in ways that break FrameworkProvider.
+    }
+
+}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/LauncherTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -72,7 +72,6 @@
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
 import com.redhat.thermostat.common.ApplicationInfo;
@@ -93,6 +92,7 @@
 import com.redhat.thermostat.common.tools.ApplicationState;
 import com.redhat.thermostat.common.tools.BasicCommand;
 import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.launcher.BundleManager;
 import com.redhat.thermostat.launcher.internal.LauncherImpl.LoggingInitializer;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.test.StubBundleContext;
@@ -144,7 +144,7 @@
     private StubBundleContext bundleContext;
     private Bundle sysBundle;
     private TestTimerFactory timerFactory;
-    private OSGiRegistry registry;
+    private BundleManager registry;
     private LoggingInitializer loggingInitializer;
     private DbServiceFactory dbServiceFactory;
     private CommandInfoSource infos;
@@ -211,7 +211,7 @@
 
         ctxFactory.getCommandRegistry().registerCommands(Arrays.asList(new HelpCommand(), cmd1, cmd2, cmd3, basicCmd));
 
-        registry = mock(OSGiRegistry.class);
+        registry = mock(BundleManager.class);
 
         infos = mock(CommandInfoSource.class);
         when(infos.getCommandInfo(name1)).thenReturn(info1);
--- a/main/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/pom.xml	Mon Jan 14 14:04:50 2013 -0500
@@ -84,11 +84,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-bundles</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-launcher</artifactId>
       <version>${project.version}</version>
     </dependency>
--- a/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/src/main/java/com/redhat/thermostat/main/Thermostat.java	Mon Jan 14 14:04:50 2013 -0500
@@ -62,6 +62,7 @@
         this.context = context;
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     private void launch()
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
             FileNotFoundException, IOException, BundleException, InterruptedException {
--- a/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/src/main/java/com/redhat/thermostat/main/impl/FrameworkProvider.java	Mon Jan 14 14:04:50 2013 -0500
@@ -59,10 +59,9 @@
 import org.osgi.framework.launch.FrameworkFactory;
 import org.osgi.util.tracker.ServiceTracker;
 
-import com.redhat.thermostat.bundles.OSGiRegistry;
 import com.redhat.thermostat.common.Configuration;
-import com.redhat.thermostat.common.ConfigurationException;
 import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
 
 public class FrameworkProvider {
 
@@ -217,11 +216,11 @@
                 locations.add(location);
             }
         }
-        OSGiRegistry.preLoadBundles(framework, locations, printOSGiInfo);
+        BundleManager.preLoadBundles(framework, locations, printOSGiInfo);
     }
 
     private void setLoaderVerbosity(Framework framework) throws InterruptedException {
-        Object loader = getService(framework, OSGiRegistry.class.getName());
+        Object loader = getService(framework, BundleManager.class.getName());
         callVoidReflectedMethod(loader, "setPrintOSGiInfo", printOSGiInfo, Boolean.TYPE);
     }
 
@@ -232,6 +231,7 @@
 
     private Object getService(Framework framework, String name) throws InterruptedException {
         Object service = null;
+        @SuppressWarnings({ "unchecked", "rawtypes" })
         ServiceTracker tracker = new ServiceTracker(framework.getBundleContext(), name, null);
         tracker.open();
         service = tracker.waitForService(0);
--- a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -1,7 +1,6 @@
 bundles=thermostat-keyring-${project.version}.jar, \
         thermostat-storage-core-${project.version}.jar, \
         thermostat-common-core-${project.version}.jar, \
-        thermostat-bundles-${project.version}.jar, \
         thermostat-launcher-${project.version}.jar, \
         thermostat-main-${project.version}.jar, \
         jline2.jar, \
--- a/main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/src/test/java/com/redhat/thermostat/main/ThermostatTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -36,27 +36,36 @@
 
 package com.redhat.thermostat.main;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Hashtable;
 
 import org.junit.Before;
+import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import com.redhat.thermostat.bundles.impl.OSGiRegistryImpl;
-import com.redhat.thermostat.common.Configuration;
+import com.redhat.thermostat.launcher.Launcher;
+import com.redhat.thermostat.launcher.BundleManager;
+import com.redhat.thermostat.main.impl.FrameworkProvider;
 
 @RunWith(PowerMockRunner.class)
-@PrepareForTest(value = Thermostat.class)
+@PrepareForTest({FrameworkProvider.class})
 public class ThermostatTest {
 
     private Path tempDir;
@@ -65,12 +74,9 @@
 
     private BundleContext mockContext;
 
+    @SuppressWarnings("rawtypes")
     @Before
     public void setUp() throws Exception {
-
-        final OSGiRegistryImpl osgiRegistry = mock(OSGiRegistryImpl.class);
-        PowerMockito.whenNew(OSGiRegistryImpl.class).withArguments(any(Configuration.class)).thenReturn(osgiRegistry);
-
         tempDir = Files.createTempDirectory("test");
         tempDir.toFile().deleteOnExit();
         System.setProperty("THERMOSTAT_HOME", tempDir.toString());
@@ -93,19 +99,40 @@
         
         mockContext = mock(BundleContext.class);
 
-        mockFramework = mock(Framework.class);
-        when(mockFramework.getBundleContext()).thenReturn(mockContext);
-
-        TestFrameworkFactory.setFramework(mockFramework);
+        Framework framework = mock(Framework.class);
+        TestFrameworkFactory.setFramework(framework);
+        when(framework.getBundleContext()).thenReturn(mockContext);
+        Bundle mockBundle = mock(Bundle.class);
+        when(mockContext.installBundle(any(String.class))).thenReturn(mockBundle);
+        when(mockBundle.getHeaders()).thenReturn(new Hashtable<String, String>());
+        ServiceTracker registryTracker = mock(ServiceTracker.class);
+        PowerMockito
+                .whenNew(ServiceTracker.class)
+                .withParameterTypes(BundleContext.class, String.class,
+                        ServiceTrackerCustomizer.class)
+                .withArguments(any(BundleContext.class),
+                        eq(BundleManager.class.getName()), any(ServiceTrackerCustomizer.class))
+                .thenReturn(registryTracker);
+        when(registryTracker.waitForService(0)).thenReturn(mock(BundleManager.class));
+        ServiceTracker launcherTracker = mock(ServiceTracker.class);
+        Launcher launcher = mock(Launcher.class);
+        PowerMockito
+                .whenNew(ServiceTracker.class)
+                .withParameterTypes(BundleContext.class, String.class,
+                        ServiceTrackerCustomizer.class)
+                .withArguments(any(BundleContext.class),
+                        eq(Launcher.class.getName()),
+                        any(ServiceTrackerCustomizer.class))
+                .thenReturn(launcherTracker);
+        when(launcherTracker.waitForService(0))
+                .thenReturn(launcher);
     }
 
-    // TODO These now seem to belong in OSGiRegistryTest
-
-    /*
     @Test
     public void testOSGIDirExists() throws Exception {
-        Path osgiDir = tempDir.resolve("osgi");
+        Path osgiDir = tempDir.resolve("osgi-cache");
         osgiDir.toFile().mkdirs();
+        osgiDir.toFile().deleteOnExit();
         assertTrue(osgiDir.toFile().exists());
         try {
             Thermostat.main(new String[0]);
@@ -113,20 +140,18 @@
             e.printStackTrace();
         }
         assertTrue(osgiDir.toFile().exists());
-    }*/
-
-    /*@Test
-    public void testFrameworkConfig() throws Exception {
-        Thermostat.main(new String[0]);
-        Map<String,String> config = TestFrameworkFactory.getConfig();
-        Path osgiDir = tempDir.resolve("osgi");
-        assertEquals(osgiDir.toString(), config.get(Constants.FRAMEWORK_STORAGE));
     }
 
     @Test
     public void testFrameworkInitAndStart() throws Exception {
+        Path osgiDir = tempDir.resolve("osgi-cache");
+        osgiDir.toFile().mkdirs();
+        osgiDir.toFile().deleteOnExit();
+        mockFramework = mock(Framework.class);
+        when(mockFramework.getBundleContext()).thenReturn(mockContext);
+        TestFrameworkFactory.setFramework(mockFramework);
         Thermostat.main(new String[0]);
         verify(mockFramework).init();
         verify(mockFramework).start();
-    }*/
+    }
 }
--- a/main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory	Wed Jan 09 14:59:30 2013 -0500
+++ b/main/src/test/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory	Mon Jan 14 14:04:50 2013 -0500
@@ -34,4 +34,4 @@
 # to do so, delete this exception statement from your version.
 #
 
-com.redhat.thermostat.launcher.TestFrameworkFactory
\ No newline at end of file
+com.redhat.thermostat.main.TestFrameworkFactory
\ No newline at end of file
--- a/pom.xml	Wed Jan 09 14:59:30 2013 -0500
+++ b/pom.xml	Mon Jan 14 14:04:50 2013 -0500
@@ -120,7 +120,6 @@
     <module>distribution</module>
     <module>main</module>
     <module>launcher</module>
-    <module>bundles</module>
     <module>common</module>
     <module>agent</module>
     <module>client</module>
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/AbstractQuery.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/AbstractQuery.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,7 +41,9 @@
 import java.util.List;
 import java.util.Objects;
 
-public abstract class AbstractQuery implements Query {
+import com.redhat.thermostat.storage.model.Pojo;
+
+public abstract class AbstractQuery<T extends Pojo> implements Query<T> {
 
     public static class Sort {
         private Key<?> key;
@@ -93,9 +95,8 @@
     }
 
     @Override
-    public <T> Query sort(Key<T> key, SortDirection direction) {
+    public void sort(Key<?> key, SortDirection direction) {
         sorts.add(new Sort(key, direction));
-        return this;
     }
 
     public List<Sort> getSorts() {
@@ -107,9 +108,8 @@
     }
 
     @Override
-    public Query limit(int limit) {
+    public void limit(int limit) {
         this.limit  = limit;
-        return this;
     }
 
     public int getLimit() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Add.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,42 @@
+/*
+ * 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.storage.core;
+
+public interface Add extends Put {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/BasePut.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.storage.core;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+
+public abstract class BasePut implements Put {
+
+    private Category category;
+    private Pojo pojo;
+
+    public final void setCategory(Category category) {
+        if (this.category != null) {
+            throw new IllegalStateException();
+        }
+        this.category = category;
+    }
+
+    public final Category getCategory() {
+        return category;
+    }
+
+    @Override
+    public final void setPojo(Pojo pojo) {
+        this.pojo = pojo;
+    }
+
+    public final Pojo getPojo() {
+        return pojo;
+    }
+
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Category.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Category.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,15 +42,20 @@
 import java.util.Map;
 import java.util.Objects;
 
+import com.redhat.thermostat.storage.model.Pojo;
+
 /**
  * A bag of data
  */
-public class Category {
+public class Category<T extends Pojo> {
+
     private String name;
     private final Map<String, Key<?>> keys;
+    private transient Class<T> dataClass;
+    private String dataClassName;
 
     public Category() {
-        this(null);
+        this(null, null);
     }
 
     /**
@@ -60,20 +65,21 @@
      *
      * @throws IllegalArgumentException if a Category is created with a name that has been used before
      */
-    public Category(String name, Key<?>... keys) {
+    public Category(String name, Class<T> dataClass, Key<?>... keys) {
         Map<String, Key<?>> keysMap = new HashMap<String, Key<?>>();
         for (Key<?> key : keys) {
             keysMap.put(key.getName(), key);
         }
         this.keys = Collections.unmodifiableMap(keysMap);
         setName(name);
+        setDataClass(dataClass);
     }
 
     public String getName() {
         return name;
     }
 
-    public void setName(String name) {
+    private void setName(String name) {
         if (Categories.contains(name)) {
             throw new IllegalStateException();
         }
@@ -85,6 +91,29 @@
         }
     }
 
+    private void setDataClass(Class<T> dataClass) {
+        this.dataClass = dataClass;
+        if (dataClass != null) {
+            dataClassName = dataClass.getName();
+        }
+    }
+
+    public Class<T> getDataClass() {
+        if (dataClass == null && dataClassName != null) {
+            initializeDataClassFromName();
+        }
+        return dataClass;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void initializeDataClassFromName() {
+        try {
+            dataClass = (Class<T>) Class.forName(dataClassName);
+        } catch (ClassNotFoundException e) {
+            throw new StorageException(e);
+        }
+    }
+
     public synchronized Collection<Key<?>> getKeys() {
         return keys.values();
     }
@@ -105,7 +134,7 @@
         if (! (o instanceof Category)) {
             return false;
         }
-        Category other = (Category) o;
+        Category<?> other = (Category<?>) o;
         return Objects.equals(name, other.name) && Objects.equals(keys, other.keys);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Put.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.storage.core;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+
+public interface Put {
+
+    /**
+     * Sets the POJO that is to be put into the database.
+     * Notice that this is usually not necessary to call, because Storage.add() 
+     * and Storage.replace() already take this argument. It is useful
+     * for pre-building and caching queries though.
+     *
+     * @param the pojo to be put into the database
+     */
+    void setPojo(Pojo pojo);
+
+    /**
+     * Applies this put operation to the database.
+     */
+    void apply();
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Mon Jan 14 14:04:50 2013 -0500
@@ -36,10 +36,12 @@
 
 package com.redhat.thermostat.storage.core;
 
+import com.redhat.thermostat.storage.model.Pojo;
+
 /**
  * Describes what data should be fetched.
  */
-public interface Query {
+public interface Query<T extends Pojo> {
 
     enum Criteria {
         EQUALS,
@@ -65,11 +67,12 @@
         }
     }
 
-    Query from(Category category);
+    <S> void where(Key<S> key, Criteria criteria, S value);
 
-    <T> Query where(Key<T> key, Criteria criteria, T value);
+    void sort(Key<?> key, SortDirection direction);
 
-    <T> Query sort(Key<T> key, SortDirection direction);
+    void limit(int n);
 
-    Query limit(int n);
+    Cursor<T> execute();
+
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,11 +42,60 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-import com.redhat.thermostat.storage.model.AgentIdPojo;
 import com.redhat.thermostat.storage.model.Pojo;
 
 public final class QueuedStorage implements Storage {
 
+    private class QueuedReplace extends BasePut implements Replace {
+
+        @Override
+        public void apply() {
+            replaceImpl(getCategory(), getPojo());
+        }
+        
+    }
+
+    private class QueuedAdd extends BasePut implements Add {
+
+        @Override
+        public void apply() {
+            addImpl(getCategory(), getPojo());
+        }
+        
+    }
+
+    private class QueuedUpdate implements Update {
+        private Update delegateUpdate;
+
+        QueuedUpdate(Update delegateUpdate) {
+            this.delegateUpdate = delegateUpdate;
+        }
+
+        @Override
+        public <T> void where(Key<T> key, T value) {
+            delegateUpdate.where(key,  value);
+            
+        }
+
+        @Override
+        public <T> void set(Key<T> key, T value) {
+            delegateUpdate.set(key, value);
+        }
+
+        @Override
+        public void apply() {
+            executor.execute(new Runnable() {
+                
+                @Override
+                public void run() {
+                    delegateUpdate.apply();
+                }
+
+            });
+        }
+
+    }
+
     private Storage delegate;
     private ExecutorService executor;
     private ExecutorService fileExecutor;
@@ -79,27 +128,43 @@
     }
 
     @Override
-    public void putPojo(final Category category, final boolean replace, final AgentIdPojo pojo) {
+    public Add createAdd(Category<?> into) {
+        QueuedAdd add = new QueuedAdd();
+        add.setCategory(into);
+        return add;
+    }
 
+    @Override
+    public Replace createReplace(Category<?> into) {
+        QueuedReplace replace = new QueuedReplace();
+        replace.setCategory(into);
+        return replace;
+    }
+
+    private void replaceImpl(final Category<?> category, final Pojo pojo) {
+        
         executor.execute(new Runnable() {
             
             @Override
             public void run() {
-                delegate.putPojo(category, replace, pojo);
+                Replace replace = delegate.createReplace(category);
+                replace.setPojo(pojo);
+                replace.apply();
             }
 
         });
 
     }
 
-    @Override
-    public void updatePojo(final Update update) {
-
+    private void addImpl(final Category<?> category, final Pojo pojo) {
+        
         executor.execute(new Runnable() {
             
             @Override
             public void run() {
-                delegate.updatePojo(update);
+                Add add = delegate.createAdd(category);
+                add.setPojo(pojo);
+                add.apply();
             }
 
         });
@@ -134,14 +199,8 @@
 
     }
 
-    @Override
-    public <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
-        return delegate.findAllPojos(query, resultClass);
-    }
-
-    @Override
-    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) {
-        return delegate.findPojo(query, resultClass);
+    <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
+        return query.execute();
     }
 
     @Override
@@ -169,13 +228,14 @@
     }
 
     @Override
-    public Query createQuery() {
-        return delegate.createQuery();
+    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
+        return delegate.createQuery(category);
     }
 
     @Override
-    public Update createUpdate() {
-        return delegate.createUpdate();
+    public Update createUpdate(Category<?> category) {
+        QueuedUpdate update = new QueuedUpdate(delegate.createUpdate(category));
+        return update;
     }
 
     @Override
@@ -203,7 +263,7 @@
     }
 
     @Override
-    public void registerCategory(final Category category) {
+    public void registerCategory(final Category<?> category) {
         delegate.registerCategory(category);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Replace.java	Mon Jan 14 14:04:50 2013 -0500
@@ -0,0 +1,42 @@
+/*
+ * 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.storage.core;
+
+public interface Replace extends Put {
+
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,7 +39,6 @@
 import java.io.InputStream;
 import java.util.UUID;
 
-import com.redhat.thermostat.storage.model.AgentIdPojo;
 import com.redhat.thermostat.storage.model.Pojo;
 
 /**
@@ -53,15 +52,12 @@
 
     String getAgentId();
 
-    void registerCategory(Category category);
+    void registerCategory(Category<?> category);
 
     Connection getConnection();
 
-    // TODO why does putPojo have an agent id param but not updatePojo and removePojo?
-
-    void putPojo(Category category, boolean replace, AgentIdPojo pojo);
-
-    void updatePojo(Update update);
+    Add createAdd(Category<?> category);
+    Replace createReplace(Category<?> category);
 
     void removePojo(Remove remove);
 
@@ -70,20 +66,18 @@
      */
     void purge();
 
-    <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass);
-
-    <T extends Pojo> T findPojo(Query query, Class<T> resultClass);
-
-    long getCount(Category category);
+    long getCount(Category<?> category);
 
     void saveFile(String filename, InputStream data);
 
     InputStream loadFile(String filename);
 
-    Query createQuery();
-    Update createUpdate();
+    <T extends Pojo> Query<T> createQuery(Category<T> category);
+
+    Update createUpdate(Category<?> category);
     Remove createRemove();
 
+
     void shutdown();
 
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Update.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Update.java	Mon Jan 14 14:04:50 2013 -0500
@@ -37,11 +37,35 @@
 package com.redhat.thermostat.storage.core;
 
 /**
- * Describes which data should be updated with what values
+ * Updates fields of a database entry. 
  */
 public interface Update {
 
-    Update from(Category category);
-    <T> Update where(Key<T> key, T value);
-    <T> Update set(Key<T> key, T value);
+    /**
+     * Adds a where clause that denotes the entry to be updated. If more than one
+     * where-clause is declared, they are concatenated as an and-query.
+     * If a clause with the same key is declared more than once, the latter
+     * overrides the former. This is so that an Update object can be reused
+     * for multiple requests. If an update is issued for which no entry can
+     * be found (i.e. the where-clause yields no results), a
+     * <code>StorageException</code> may get thrown.
+     *
+     * @param key the key of the field of the where clause
+     * @param value the value of the field of the where clause
+     */
+    <T> void where(Key<T> key, T value);
+
+    /**
+     * Sets a field in a found document to the specified value. If the same key is
+     * set more than once, the latest values overrides the former values.
+     *
+     * @param key the key of the field
+     * @param value the value to set
+     */
+    <T> void set(Key<T> key, T value);
+
+    /**
+     * Applies the update operation.
+     */
+    void apply();
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/model/AgentIdPojo.java	Wed Jan 09 14:59:30 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +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.storage.model;
-
-public interface AgentIdPojo extends Pojo {
-
-    void setAgentId(String agentId);
-    String getAgentId();
-}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/model/BasePojo.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/model/BasePojo.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,7 +41,7 @@
 
 import com.redhat.thermostat.storage.core.Persist;
 
-public class BasePojo implements AgentIdPojo {
+public class BasePojo implements Pojo {
 
     private String agentId;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/BasePutTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -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.storage.core;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class BasePutTest {
+
+    private BasePut insert;
+
+    @Before
+    public void setUp() {
+        insert = new BasePut() {
+            public void apply() {
+                // Do nothing.
+            }
+        };
+    }
+
+    @After
+    public void tearDown() {
+        insert = null;
+    }
+
+    @Test
+    public void testCategory() {
+        Category category = mock(Category.class);
+
+        assertNull(insert.getCategory());
+        insert.setCategory(category);
+        assertSame(category, insert.getCategory());
+
+        try {
+            insert.setCategory(category);
+            fail();
+        } catch (IllegalStateException ex) {
+            // Ok.
+        }
+    }
+
+    @Test
+    public void testPojo() {
+        Pojo pojo = mock(Pojo.class);
+
+        assertNull(insert.getPojo());
+        insert.setPojo(pojo);
+        assertSame(pojo, insert.getPojo());
+        
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/core/CategoryTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/CategoryTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -44,22 +44,25 @@
 
 import org.junit.Test;
 
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.model.Pojo;
 
 public class CategoryTest {
 
+    private static class TestObj implements Pojo {
+        // Dummy class for testing.
+    }
+
     @Test
     public void testGetKey() {
         Key<String> key1 = new Key<String>("key1", false);
-        Category category = new Category("testGetKey", key1);
+        Category<TestObj> category = new Category<>("testGetKey", TestObj.class, key1);
         assertEquals(key1, category.getKey("key1"));
     }
 
     @Test
     public void testGetNonExistingKey() {
         Key<String> key1 = new Key<String>("key1", false);
-        Category category = new Category("testGetNonExistingKey", key1);
+        Category<TestObj> category = new Category<>("testGetNonExistingKey", TestObj.class, key1);
         assertNull(category.getKey("key2"));
     }
 
@@ -69,7 +72,7 @@
         Key<String> key2 = new Key<String>("key2", false);
         Key<String> key3 = new Key<String>("key3", false);
         Key<String> key4 = new Key<String>("key4", false);
-        Category category = new Category("testGetKeys", key1, key2, key3, key4);
+        Category<TestObj> category = new Category<>("testGetKeys", TestObj.class, key1, key2, key3, key4);
         assertEquals(4, category.getKeys().size());
         assertTrue(category.getKeys().contains(key1));
         assertTrue(category.getKeys().contains(key2));
@@ -82,7 +85,7 @@
         Key<String> key1 = new Key<String>("key1", false);
         Key<String> key2 = new Key<String>("key2", false);
         Key<String> key3 = new Key<String>("key3", false);
-        Category category = new Category("verifyThatKeysAreUnmodifiable", key1, key2, key3);
+        Category<TestObj> category = new Category<>("verifyThatKeysAreUnmodifiable", TestObj.class, key1, key2, key3);
 
         Collection<Key<?>> keys = category.getKeys();
 
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/core/QueuedStorageTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -67,7 +67,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import com.redhat.thermostat.storage.model.AgentIdPojo;
 import com.redhat.thermostat.storage.model.Pojo;
 
 
@@ -165,6 +164,7 @@
             // Not used.
             return null;
         }
+
     }
 
     private static class TestPojo implements Pojo {
@@ -173,13 +173,15 @@
 
     private QueuedStorage queuedStorage;
     private Storage delegateStorage;
+    private Add delegateAdd;
+    private Replace delegateReplace;
+    private Query<?> delegateQuery;
 
     private TestExecutor executor;
     private TestExecutor fileExecutor;
 
     @SuppressWarnings("rawtypes")
     private Cursor expectedResults;
-    private TestPojo expectedResult;
     private InputStream expectedFile;
 
     @SuppressWarnings("unchecked")
@@ -188,16 +190,18 @@
         executor = new TestExecutor();
         fileExecutor = new TestExecutor();
         delegateStorage = mock(Storage.class);
-        Update update = mock(Update.class);
+
+        delegateAdd = mock(Add.class);
+        delegateReplace = mock(Replace.class);
+
         Remove remove = mock(Remove.class);
-        Query query = mock(Query.class);
-        when(delegateStorage.createUpdate()).thenReturn(update);
+        delegateQuery = mock(Query.class);
+        when(delegateStorage.createAdd(any(Category.class))).thenReturn(delegateAdd);
+        when(delegateStorage.createReplace(any(Category.class))).thenReturn(delegateReplace);
         when(delegateStorage.createRemove()).thenReturn(remove);
-        when(delegateStorage.createQuery()).thenReturn(query);
+        when(delegateStorage.createQuery(any(Category.class))).thenReturn(delegateQuery);
         expectedResults = mock(Cursor.class);
-        when(delegateStorage.findAllPojos(query, TestPojo.class)).thenReturn(expectedResults);
-        expectedResult = new TestPojo();
-        when(delegateStorage.findPojo(query, TestPojo.class)).thenReturn(expectedResult);
+        when(delegateQuery.execute()).thenReturn(expectedResults);
         when(delegateStorage.getCount(any(Category.class))).thenReturn(42l);
         expectedFile = mock(InputStream.class);
         when(delegateStorage.loadFile(anyString())).thenReturn(expectedFile);
@@ -209,26 +213,32 @@
     @After
     public void tearDown() {
         expectedFile = null;
-        expectedResult = null;
         expectedResults = null;
         queuedStorage = null;
         delegateStorage = null;
         fileExecutor = null;
         executor = null;
+        delegateQuery = null;
     }
 
     @Test
-    public void testPutPojo() {
-        Category category = mock(Category.class);
-        AgentIdPojo pojo = mock(AgentIdPojo.class);
+    public void testInsert() {
+        Category<?> category = mock(Category.class);
+        Pojo pojo = mock(Pojo.class);
 
-        queuedStorage.putPojo(category, true, pojo);
+        Put put = queuedStorage.createReplace(category);
+        put.setPojo(pojo);
+        put.apply();
 
         Runnable r = executor.getTask();
         assertNotNull(r);
         verifyZeroInteractions(delegateStorage);
+        verifyZeroInteractions(delegateReplace);
+
         r.run();
-        verify(delegateStorage, times(1)).putPojo(category, true, pojo);
+        verify(delegateStorage).createReplace(category);
+        verify(delegateReplace).setPojo(pojo);
+        verify(delegateReplace).apply();
         verifyNoMoreInteractions(delegateStorage);
 
         assertNull(fileExecutor.getTask());
@@ -236,19 +246,23 @@
 
     @Test
     public void testUpdatePojo() {
+        Update delegateUpdate = mock(Update.class);
+        when(delegateStorage.createUpdate(any(Category.class))).thenReturn(delegateUpdate);
 
-        Update update = queuedStorage.createUpdate();
-        verify(delegateStorage).createUpdate();
+        Category<?> category = mock(Category.class);
+
+        Update update = queuedStorage.createUpdate(category);
+        verify(delegateStorage).createUpdate(category);
         verifyNoMoreInteractions(delegateStorage);
 
-        queuedStorage.updatePojo(update);
+        update.apply();
 
         Runnable r = executor.getTask();
         assertNotNull(r);
-        verifyZeroInteractions(delegateStorage);
+        verifyZeroInteractions(delegateUpdate);
         r.run();
-        verify(delegateStorage, times(1)).updatePojo(update);
-        verifyNoMoreInteractions(delegateStorage);
+        verify(delegateUpdate).apply();
+        verifyNoMoreInteractions(delegateUpdate);
 
         assertNull(fileExecutor.getTask());
     }
@@ -289,12 +303,14 @@
 
     @Test
     public void testFindAllPojos() {
-        Query query = queuedStorage.createQuery();
-        verify(delegateStorage).createQuery();
+        @SuppressWarnings("unchecked")
+        Category<TestPojo> category = mock(Category.class);
+        Query<TestPojo> query = queuedStorage.createQuery(category);
+        verify(delegateStorage).createQuery(category);
         verifyNoMoreInteractions(delegateStorage);
 
-        Cursor<TestPojo> result = queuedStorage.findAllPojos(query, TestPojo.class);
-        verify(delegateStorage).findAllPojos(query, TestPojo.class);
+        Cursor<TestPojo> result = query.execute();
+        verify(delegateQuery).execute();
         assertSame(expectedResults, result);
 
         assertNull(executor.getTask());
@@ -302,22 +318,8 @@
     }
 
     @Test
-    public void testFindPojo() {
-        Query query = queuedStorage.createQuery();
-        verify(delegateStorage).createQuery();
-        verifyNoMoreInteractions(delegateStorage);
-
-        TestPojo result = queuedStorage.findPojo(query, TestPojo.class);
-        verify(delegateStorage).findPojo(query, TestPojo.class);
-        assertSame(expectedResult, result);
-
-        assertNull(executor.getTask());
-        assertNull(fileExecutor.getTask());
-    }
-
-    @Test
     public void testGetCount() {
-        Category category = mock(Category.class);
+        Category<?> category = mock(Category.class);
 
         long result = queuedStorage.getCount(category);
         assertEquals(42, result);
@@ -380,7 +382,7 @@
     @Test
     public void testRegisterCategory() {
 
-        Category category = mock(Category.class);
+        Category<?> category = mock(Category.class);
 
         queuedStorage.registerCategory(category);
 
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoQuery.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,34 +42,38 @@
 import com.mongodb.DBObject;
 import com.redhat.thermostat.storage.core.AbstractQuery;
 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.model.Pojo;
 
-public class MongoQuery extends AbstractQuery {
+public class MongoQuery<T extends Pojo> extends AbstractQuery<T> {
 
+    private MongoStorage storage;
     private BasicDBObject query = new BasicDBObject();
     private boolean hasClauses = false;
-    private Category category;
+    private Category<T> category;
+    private Class<T> resultClass;
 
-    @Override
-    public MongoQuery from(Category category) {
-        setCategory(category);
-        return this;
+    MongoQuery(MongoStorage storage, Category<T> category) {
+        this.storage = storage;
+        this.category = category;
+        this.resultClass = category.getDataClass();
     }
 
-    public Category getCategory() {
+    public Category<T> getCategory() {
         return category;
     }
 
-    public void setCategory(Category category) {
+    public void setCategory(Category<T> category) {
         this.category = category;
     }
 
     @Override
-    public <T> MongoQuery where(Key<T> key, Criteria operator, T value) {
-        return where(key.getName(), operator, value);
+    public <S> void where(Key<S> key, Criteria operator, S value) {
+        where(key.getName(), operator, value);
     }
 
-    public MongoQuery where(String key, Criteria operator, Object value) {
+    public void where(String key, Criteria operator, Object value) {
         switch (operator) {
         case EQUALS:
             query.put(key, value);
@@ -97,7 +101,6 @@
             throw new IllegalArgumentException("MongoQuery can not handle " + operator);
         }
         hasClauses = true;
-        return this;
     }
 
     DBObject getGeneratedQuery() {
@@ -115,7 +118,7 @@
         if (!(obj instanceof MongoQuery)) {
             return false;
         }
-        MongoQuery other = (MongoQuery) obj;
+        MongoQuery<?> other = (MongoQuery<?>) obj;
         return Objects.equals(getCategory(), other.getCategory()) && Objects.equals(this.query, other.query);
     }
 
@@ -128,4 +131,8 @@
         return hasClauses ;
     }
 
+    @Override
+    public Cursor<T> execute() {
+        return storage.findAllPojos(this, resultClass);
+    }
 }
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Mon Jan 14 14:04:50 2013 -0500
@@ -53,6 +53,8 @@
 import com.mongodb.gridfs.GridFSInputFile;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
 import com.redhat.thermostat.storage.core.AbstractQuery.Sort;
+import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BasePut;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
@@ -61,9 +63,9 @@
 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.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.model.AgentIdPojo;
 import com.redhat.thermostat.storage.model.Pojo;
 
 /**
@@ -73,7 +75,23 @@
  */
 public class MongoStorage implements Storage {
 
-    public static final String SET_MODIFIER = "$set";
+    private class MongoAdd extends BasePut implements Add {
+
+        @Override
+        public void apply() {
+            addImpl(getCategory(), getPojo());
+        }
+        
+    }
+
+    private class MongoReplace extends BasePut implements Replace {
+
+        @Override
+        public void apply() {
+            replaceImpl(getCategory(), getPojo());
+        }
+        
+    }
 
     private MongoConnection conn;
     private DB db = null;
@@ -113,52 +131,52 @@
         return agentId.toString();
     }
 
-    private String getAgentQueryKeyFromGlobalAgent() {
-        if (agentId != null) {
-            return agentId.toString();
-        } else {
-            return null;
-        }
-    }
-
-    private String getAgentQueryKeyFromChunkOrGlobalAgent(AgentIdPojo pojo) {
-        String queryKey = getAgentQueryKeyFromGlobalAgent();
-        if (queryKey != null) {
-            return queryKey;
-        } else {
-            return pojo.getAgentId();
-        }
+    @Override
+    public Add createAdd(Category<?> into) {
+        MongoAdd add = new MongoAdd();
+        add.setCategory(into);
+        return add;
     }
 
     @Override
-    public void putPojo(final Category cat, final boolean replace, final AgentIdPojo pojo) {
+    public Replace createReplace(Category<?> into) {
+        MongoReplace replace = new MongoReplace();
+        replace.setCategory(into);
+        return replace;
+    }
+
+    private void addImpl(final Category<?> cat, final Pojo pojo) {
+        DBCollection coll = getCachedCollection(cat);
+        DBObject toInsert = preparePut(pojo);
+        coll.insert(toInsert);
+    }
+
+    private void replaceImpl(final Category<?> cat, final Pojo pojo) {
         DBCollection coll = getCachedCollection(cat);
+        DBObject toInsert = preparePut(pojo);
+
+        DBObject query = new BasicDBObject();
+        Collection<Key<?>> keys = cat.getKeys();
+        for (Key<?> key : keys) {
+            if (key.isPartialCategoryKey()) {
+                String name = key.getName();
+                query.put(name, toInsert.get(name));
+            }
+        }
+        coll.update(query, toInsert, true, false);
+    }
+
+    private DBObject preparePut(final Pojo pojo) {
         MongoPojoConverter converter = new MongoPojoConverter();
         DBObject toInsert = converter.convertPojoToMongo(pojo);
-        String agentId = getAgentQueryKeyFromChunkOrGlobalAgent(pojo);
-        toInsert.put(Key.AGENT_ID.getName(), agentId);
-        if (replace) {
-            // TODO: Split this part out into a separate method. It is a very bad practice to
-            // completely change the behaviour of a method based on a boolean flag.
-            DBObject query = new BasicDBObject();
-            Collection<Key<?>> keys = cat.getKeys();
-            for (Key<?> key : keys) {
-                if (key.isPartialCategoryKey()) {
-                    String name = key.getName();
-                    query.put(name, toInsert.get(name));
-                }
-            }
-            coll.update(query, toInsert, true, false);
-        } else {
-            coll.insert(toInsert);
+        if (toInsert.get(Key.AGENT_ID.getName()) == null) {
+            toInsert.put(Key.AGENT_ID.getName(), getAgentId());
         }
+        return toInsert;
     }
 
-    @Override
-    public void updatePojo(Update update) {
-        assert update instanceof MongoUpdate;
-        MongoUpdate mongoUpdate = (MongoUpdate) update;
-        Category cat = mongoUpdate.getCategory();
+    void updatePojo(MongoUpdate mongoUpdate) {
+        Category<?> cat = mongoUpdate.getCategory();
         DBCollection coll = getCachedCollection(cat);
         DBObject query = mongoUpdate.getQuery();
         DBObject values = mongoUpdate.getValues();
@@ -170,18 +188,13 @@
         assert (remove instanceof MongoRemove);
         MongoRemove mongoRemove = (MongoRemove) remove;
         DBObject query = mongoRemove.getQuery();
-        Category category = mongoRemove.getCategory();
+        Category<?> category = mongoRemove.getCategory();
         DBCollection coll = getCachedCollection(category);
 
-        String agentId = getAgentQueryKeyFromGlobalAgent();
-        if (agentId != null) {
-            query.put(Key.AGENT_ID.getName(), agentId);
-        }
-
         coll.remove(query);
     }
 
-    private DBCollection getCachedCollection(Category category) {
+    private DBCollection getCachedCollection(Category<?> category) {
         String collName = category.getName();
         DBCollection coll = collectionCache.get(collName);
         if (coll == null && db.collectionExists(collName)) {
@@ -193,22 +206,21 @@
     // TODO: This method is only temporary to enable tests, until we come up with a better design,
     // in particular, the collection should be stored in the category itself. It must not be called
     // from production code.
-    void mapCategoryToDBCollection(Category category, DBCollection coll) {
+    void mapCategoryToDBCollection(Category<?> category, DBCollection coll) {
         collectionCache.put(category.getName(), coll);
     }
 
 
     @Override
     public void purge() {
-        String deleteKey = getAgentQueryKeyFromGlobalAgent();
-        BasicDBObject query = new BasicDBObject(Key.AGENT_ID.getName(), deleteKey);
+        BasicDBObject query = new BasicDBObject(Key.AGENT_ID.getName(), getAgentId());
         for (DBCollection coll : collectionCache.values()) {
             coll.remove(query);
         }
     }
     
     @Override
-    public void registerCategory(Category category) {
+    public void registerCategory(Category<?> category) {
         String name = category.getName();
         if (collectionCache.containsKey(name)) {
             throw new IllegalStateException("Category may only be associated with one backend.");
@@ -224,13 +236,13 @@
     }
 
     @Override
-    public Query createQuery() {
-        return new MongoQuery();
+    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
+        return new MongoQuery<T>(this, category);
     }
 
     @Override
-    public Update createUpdate() {
-        return new MongoUpdate();
+    public Update createUpdate(Category<?> category) {
+        return new MongoUpdate(this, category);
     }
 
     @Override
@@ -238,9 +250,7 @@
         return new MongoRemove();
     }
 
-    @Override
-    public <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
-        MongoQuery mongoQuery =  checkAndCastQuery(query);
+    <T extends Pojo> Cursor<T> findAllPojos(MongoQuery<T> mongoQuery, Class<T> resultClass) {
         DBCollection coll = getCachedCollection(mongoQuery.getCategory());
         DBCursor dbCursor;
         if (mongoQuery.hasClauses()) {
@@ -252,7 +262,7 @@
         return new MongoCursor<T>(dbCursor, resultClass);
     }
 
-    private DBCursor applySortAndLimit(MongoQuery query, DBCursor dbCursor) {
+    private DBCursor applySortAndLimit(MongoQuery<?> query, DBCursor dbCursor) {
         BasicDBObject orderBy = new BasicDBObject();
         List<Sort> sorts = query.getSorts();
         for (Sort sort : sorts) {
@@ -268,25 +278,7 @@
 
 
     @Override
-    public <T extends Pojo> T findPojo(Query query, Class<T> resultClass) {
-        MongoQuery mongoQuery = checkAndCastQuery(query);
-        DBCollection coll = getCachedCollection(mongoQuery.getCategory());
-        DBObject dbResult = coll.findOne(mongoQuery.getGeneratedQuery());
-        MongoPojoConverter conv = new MongoPojoConverter();
-        return conv.convertMongoToPojo(dbResult, resultClass);
-    }
-
-    private MongoQuery checkAndCastQuery(Query query) {
-        if (!(query instanceof MongoQuery)) {
-            throw new IllegalArgumentException("MongoStorage can only handle MongoQuery");
-        }
-
-        return (MongoQuery) query;
-
-    }
-
-    @Override
-    public long getCount(Category category) {
+    public long getCount(Category<?> category) {
         DBCollection coll = getCachedCollection(category);
         if (coll != null) {
             return coll.getCount();
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoUpdate.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoUpdate.java	Mon Jan 14 14:04:50 2013 -0500
@@ -43,22 +43,18 @@
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Update;
 
-// TODO: For now we utilize the Chunk based conversion, and rely on MongoStorage to
-// actually resolve the $set fields. Eventually, we want to convert to DBObject
-// directly, and take advantage of improved semantics of this class.
 class MongoUpdate implements Update {
 
+    private static final String SET_MODIFIER = "$set";
+
+    private MongoStorage storage;
     private DBObject query;
     private DBObject values;
     private Category category;
 
-    @Override
-    public Update from(Category category) {
-        if (query != null || values != null) {
-            throw new IllegalStateException();
-        }
+    public MongoUpdate(MongoStorage storage, Category category) {
+        this.storage = storage;
         this.category = category;
-        return this;
     }
 
     Category getCategory() {
@@ -66,12 +62,11 @@
     }
 
     @Override
-    public <T> Update where(Key<T> key, T value) {
+    public <T> void where(Key<T> key, T value) {
         if (query == null) {
             query = new BasicDBObject();
         }
         query.put(key.getName(), value);
-        return this;
     }
 
     DBObject getQuery() {
@@ -79,15 +74,19 @@
     }
 
     @Override
-    public <T> Update set(Key<T> key, T value) {
+    public <T> void set(Key<T> key, T value) {
         if (values == null) {
             values = new BasicDBObject();
         }
         values.put(key.getName(), value);
-        return this;
     }
 
     DBObject getValues() {
-        return new BasicDBObject("$set", values);
+        return new BasicDBObject(SET_MODIFIER, values);
+    }
+
+    @Override
+    public void apply() {
+        storage.updatePojo(this);
     }
 }
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoCursorTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoCursorTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -52,13 +52,10 @@
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBCursor;
 import com.mongodb.DBObject;
-import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Entity;
-import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Persist;
 import com.redhat.thermostat.storage.model.BasePojo;
-import com.redhat.thermostat.storage.mongodb.internal.MongoCursor;
 
 public class MongoCursorTest {
 
@@ -102,13 +99,6 @@
         }
     }
 
-    private static final Key<String> key1 = new Key<>("key1", false);
-    private static final Key<String> key2 = new Key<>("key2", false);
-    private static final Key<String> key3 = new Key<>("key3", false);
-    private static final Key<String> key4 = new Key<>("key4", false);
-
-    private static final Category testCategory = new Category("MongoCursorTest", key1, key2, key3, key4);
-
     private DBCursor dbCursor;
     private Cursor<TestClass> cursor;
 
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoQueryTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoQueryTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -38,28 +38,50 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.mongodb.BasicDBObject;
 import com.mongodb.DBObject;
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.storage.mongodb.internal.MongoQuery;
+import com.redhat.thermostat.storage.model.Pojo;
 
 public class MongoQueryTest {
 
+    private static class TestClass implements Pojo {
+        
+    }
+
+    private static MongoStorage storage;
+    private static Category<TestClass> category;
+
+    @BeforeClass
+    public static void setUp() {
+        storage = mock(MongoStorage.class);
+        category = new Category<>("some-collection", TestClass.class);
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        storage = null;
+        category = null;
+    }
+
     @Test
     public void testEmptyQuery() {
-        MongoQuery query = new MongoQuery();
+        
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
         DBObject mongoQuery = query.getGeneratedQuery();
         assertTrue(mongoQuery.keySet().isEmpty());
     }
 
     @Test
     public void testCollectionName() {
-        MongoQuery query = new MongoQuery().from(new Category("some-collection"));
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
         assertEquals("some-collection", query.getCategory().getName());
     }
 
@@ -100,7 +122,8 @@
     }
 
     private DBObject generateSimpleWhereQuery(String key, Criteria criteria, Object value) {
-        MongoQuery query = new MongoQuery().where(key, criteria, value);
+        MongoQuery<TestClass> query = new MongoQuery<>(storage, category);
+        query.where(key, criteria, value);
         return query.getGeneratedQuery();
     }
 
--- a/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/storage/mongo/src/test/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorageTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -74,11 +74,13 @@
 import com.mongodb.gridfs.GridFSDBFile;
 import com.mongodb.gridfs.GridFSInputFile;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Entity;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Update;
@@ -142,8 +144,8 @@
     private static final Key<String> key3 = new Key<>("key3", false);
     private static final Key<String> key4 = new Key<>("key4", false);
     private static final Key<String> key5 = new Key<>("key5", false);
-    private static final Category testCategory = new Category("MongoStorageTest", key1, key2, key3, key4, key5);
-    private static final Category emptyTestCategory = new Category("MongoEmptyCategory");
+    private static final Category<TestClass> testCategory = new Category<>("MongoStorageTest", TestClass.class, key1, key2, key3, key4, key5);
+    private static final Category<TestClass> emptyTestCategory = new Category("MongoEmptyCategory", TestClass.class);
 
     private StartupConfiguration conf;
     private Mongo m;
@@ -201,57 +203,26 @@
         cursor = null;
     }
 
-    @Test (expected=IllegalArgumentException.class)
-    public void verifyFindOnlyAcceptsMongoQuery() {
-        MongoStorage storage = makeStorage();
-        Query query = mock(Query.class);
-        storage.findPojo(query, TestClass.class);
-    }
-
-    @Test (expected=IllegalArgumentException.class)
-    public void verifyFindAllOnlyAcceptsMongoQuery() {
-        MongoStorage storage = makeStorage();
-        Query query = mock(Query.class);
-        storage.findAllPojos(query, TestClass.class);
-    }
-
     @Test
     public void verifyFindAllReturnsCursor() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory);
-        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
+        Query query = storage.createQuery(testCategory);
+        Cursor<TestClass> cursor = query.execute();
         assertNotNull(cursor);
     }
 
     @Test
-    public void verifyFindReturnsChunk() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory).where(key1, Criteria.EQUALS, "test1");
-        TestClass result = storage.findPojo(query, TestClass.class);
-        assertNotNull(result);
-    }
-
-    @Test
     public void verifyFindAllCallsDBCollectionFind() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory).where(key1, Criteria.EQUALS, "fluff");
-        storage.findAllPojos(query, TestClass.class);
+        Query query = storage.createQuery(testCategory);
+        query.where(key1, Criteria.EQUALS, "fluff");
+        query.execute();
         verify(testCollection).find(any(DBObject.class));
     }
 
     @Test
-    public void verifyFindCallsDBCollectionFindOne() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory);
-        storage.findPojo(query, TestClass.class);
-        verify(testCollection).findOne(any(DBObject.class));
-    }
-
-    @Test
     public void verifyFindAllCallsDBCollectionFindWithCorrectQuery() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
@@ -261,45 +232,10 @@
         DBObject generatedQuery = mock(DBObject.class);
         when(query.getGeneratedQuery()).thenReturn(generatedQuery);
         when(query.getCategory()).thenReturn(testCategory);
-        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
 
         storage.findAllPojos(query, TestClass.class);
 
-        verify(testCollection).find(findArg.capture());
-        assertSame(generatedQuery, findArg.getValue());
-    }
-
-    @Test
-    public void verifyFindCallsDBCollectionFindOneWithCorrectQuery() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-
-        MongoQuery query = mock(MongoQuery.class);
-        DBObject generatedQuery = mock(DBObject.class);
-        when(query.getGeneratedQuery()).thenReturn(generatedQuery);
-        when(query.getCategory()).thenReturn(testCategory);
-
-        ArgumentCaptor<DBObject> findArg = ArgumentCaptor.forClass(DBObject.class);
-
-        storage.findPojo(query, TestClass.class);
-
-        verify(testCollection).findOne(findArg.capture());
-        assertSame(generatedQuery, findArg.getValue());
-    }
-
-    @Test
-    public void verifyFindReturnsCorrectChunk() throws Exception {
-        PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
-        MongoStorage storage = makeStorage();
-        // TODO find a way to test this that isn't just testing mock and converters
-        // Because we mock the DBCollection, the contents of this query don't actually determine the result.
-        MongoQuery query = new MongoQuery().from(testCategory);
-
-        TestClass result = storage.findPojo(query, TestClass.class);
-
-        assertNotNull(result);
-        assertEquals("test1", result.getKey1());
-        assertEquals("test2", result.getKey2());
+        verify(testCollection).find(same(generatedQuery));
     }
 
     @Test
@@ -308,9 +244,8 @@
         MongoStorage storage = makeStorage();
         // TODO find a way to test this that isn't just testing MongoCursor
         // Because we mock the DBCollection, the contents of this query don't actually determine the result.
-        MongoQuery query = new MongoQuery().from(testCategory);
-
-        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
+        Query query = storage.createQuery(testCategory);
+        Cursor<TestClass> cursor = query.execute();
 
         verifyDefaultCursor(cursor);
     }
@@ -321,9 +256,11 @@
         MongoStorage storage = makeStorage();
         // TODO find a way to test this that isn't just testing MongoCursor
         // Because we mock the DBCollection, the contents of this query don't actually determine the result.
-        MongoQuery query = (MongoQuery) new MongoQuery().from(testCategory).sort(key1, Query.SortDirection.ASCENDING).limit(3);
+        Query query = storage.createQuery(testCategory);
+        query.sort(key1, Query.SortDirection.ASCENDING);
+        query.limit(3);
 
-        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
+        Cursor<TestClass> cursor = query.execute();
 
         verifyDefaultCursor(cursor);
         ArgumentCaptor<DBObject> orderBy = ArgumentCaptor.forClass(DBObject.class);
@@ -337,8 +274,8 @@
     public void verifyFindAllFromCategoryCallsDBCollectionFindAll() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory);
-        storage.findAllPojos(query, TestClass.class);
+        Query query = storage.createQuery(testCategory);
+        query.execute();
         verify(testCollection).find();
     }
 
@@ -346,8 +283,8 @@
     public void verifyFindAllFromCategoryReturnsCorrectCursor() throws Exception {
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
-        Query query = storage.createQuery().from(testCategory);
-        Cursor<TestClass> cursor = storage.findAllPojos(query, TestClass.class);
+        Query query = storage.createQuery(testCategory);
+        Cursor<TestClass> cursor = query.execute();
 
         verifyDefaultCursor(cursor);
     }
@@ -373,7 +310,7 @@
         PowerMockito.whenNew(Mongo.class).withParameterTypes(MongoURI.class).withArguments(any(MongoURI.class)).thenReturn(m);
         MongoStorage storage = makeStorage();
         storage.getConnection().connect();
-        long count = storage.getCount(new Category("NonExistent"));
+        long count = storage.getCount(new Category("NonExistent", TestClass.class));
         assertEquals(0, count);
     }
 
@@ -413,7 +350,9 @@
         MongoStorage storage = makeStorage();
         TestClass pojo = new TestClass();
         pojo.setAgentId("123");
-        storage.putPojo(testCategory, false, pojo);
+        Put add = storage.createAdd(testCategory);
+        add.setPojo(pojo);
+        add.apply();
         ArgumentCaptor<DBObject> dbobj = ArgumentCaptor.forClass(DBObject.class);
         verify(testCollection).insert(dbobj.capture());
         DBObject val = dbobj.getValue();
@@ -426,8 +365,9 @@
         MongoStorage storage = makeStorage();
         storage.setAgentId(new UUID(1, 2));
         TestClass pojo = new TestClass();
-        pojo.setAgentId("123");
-        storage.putPojo(testCategory, false, pojo);
+        Add add = storage.createAdd(testCategory);
+        add.setPojo(pojo);
+        add.apply();
         ArgumentCaptor<DBObject> dbobj = ArgumentCaptor.forClass(DBObject.class);
         verify(testCollection).insert(dbobj.capture());
         DBObject val = dbobj.getValue();
@@ -455,8 +395,10 @@
     @Test
     public void verifySimpleUpdate() {
         MongoStorage storage = makeStorage();
-        Update update = storage.createUpdate().from(testCategory).where(Key.AGENT_ID, "test1").set(key2, "test2");
-        storage.updatePojo(update);
+        Update update = storage.createUpdate(testCategory);
+        update.where(Key.AGENT_ID, "test1");
+        update.set(key2, "test2");
+        update.apply();
 
         ArgumentCaptor<DBObject> queryCaptor = ArgumentCaptor.forClass(DBObject.class);
         ArgumentCaptor<DBObject> valueCaptor = ArgumentCaptor.forClass(DBObject.class);
@@ -465,13 +407,24 @@
         DBObject query = queryCaptor.getValue();
         assertTrue(query.containsField(Key.AGENT_ID.getName()));
         assertEquals("test1", query.get(Key.AGENT_ID.getName()));
+
+        DBObject set = valueCaptor.getValue();
+        assertEquals(1, set.keySet().size());
+        assertTrue(set.containsField("$set"));
+        DBObject values = (DBObject) set.get("$set");
+        assertEquals(1, values.keySet().size());
+        assertTrue(values.containsField(key2.getName()));
+        assertEquals("test2", values.get(key2.getName()));
     }
 
     @Test
     public void verifyMultiFieldUpdate() {
         MongoStorage storage = makeStorage();
-        Update update = storage.createUpdate().from(testCategory).where(Key.AGENT_ID, "test1").set(key2, "test2").set(key3, "test3");
-        storage.updatePojo(update);
+        Update update = storage.createUpdate(testCategory);
+        update.where(Key.AGENT_ID, "test1");
+        update.set(key2, "test2");
+        update.set(key3, "test3");
+        update.apply();
 
         ArgumentCaptor<DBObject> queryCaptor = ArgumentCaptor.forClass(DBObject.class);
         ArgumentCaptor<DBObject> valueCaptor = ArgumentCaptor.forClass(DBObject.class);
@@ -480,9 +433,10 @@
         DBObject query = queryCaptor.getValue();
         assertTrue(query.containsField(Key.AGENT_ID.getName()));
         assertEquals("test1", query.get(Key.AGENT_ID.getName()));
-        DBObject value = valueCaptor.getValue();
-        assertTrue(value.containsField("$set"));
-        DBObject values = (DBObject) value.get("$set");
+
+        DBObject set = valueCaptor.getValue();
+        assertTrue(set.containsField("$set"));
+        DBObject values = (DBObject) set.get("$set");
         assertTrue(values.containsField("key2"));
         assertEquals("test2", values.get("key2"));
         assertTrue(values.containsField("key3"));
@@ -500,7 +454,9 @@
         pojo.setKey5("test5");
 
         MongoStorage storage = makeStorage();
-        storage.putPojo(testCategory, true, pojo);
+        Put replace = storage.createReplace(testCategory);
+        replace.setPojo(pojo);
+        replace.apply();
 
         ArgumentCaptor<DBObject> queryCaptor = ArgumentCaptor.forClass(DBObject.class);
         ArgumentCaptor<DBObject> valueCaptor = ArgumentCaptor.forClass(DBObject.class);
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Mon Jan 14 14:04:50 2013 -0500
@@ -58,8 +58,8 @@
     static final Key<Boolean> THREAD_ALLOCATED_MEMORY_KEY = new Key<Boolean>(THREAD_ALLOCATED_MEMORY, false);
     static final Key<List<String>> SUPPORTED_FEATURES_LIST_KEY = new Key<List<String>>(SUPPORTED_FEATURES_LIST, false);
 
-    static final Category THREAD_CAPABILITIES =
-            new Category("vm-thread-capabilities", Key.AGENT_ID, Key.VM_ID,
+    static final Category<VMThreadCapabilities> THREAD_CAPABILITIES =
+            new Category<>("vm-thread-capabilities", VMThreadCapabilities.class, Key.AGENT_ID, Key.VM_ID,
                          SUPPORTED_FEATURES_LIST_KEY);
 
 
@@ -71,8 +71,8 @@
     static final String DAEMON_THREADS = "currentDaemonThreads";
     static final Key<Long> DAEMON_THREADS_KEY = new Key<Long>(DAEMON_THREADS, false);
     
-    static final Category THREAD_SUMMARY =
-            new Category("vm-thread-summary", Key.AGENT_ID, Key.VM_ID,
+    static final Category<ThreadSummary> THREAD_SUMMARY =
+            new Category<>("vm-thread-summary", ThreadSummary.class, Key.AGENT_ID, Key.VM_ID,
                          Key.TIMESTAMP,
                          LIVE_THREADS_KEY, DAEMON_THREADS_KEY);
     
@@ -95,8 +95,8 @@
     static final String THREAD_WAIT_COUNT = "threadWaitCount";
     static final Key<Long> THREAD_WAIT_COUNT_KEY = new Key<Long>(THREAD_WAIT_COUNT, false);
     
-    static final Category THREAD_INFO =
-            new Category("vm-thread-info", Key.AGENT_ID, Key.VM_ID,
+    static final Category<ThreadInfoData> THREAD_INFO =
+            new Category<>("vm-thread-info", ThreadInfoData.class, Key.AGENT_ID, Key.VM_ID,
                          Key.TIMESTAMP, THREAD_NAME_KEY, THREAD_ID_KEY,
                          THREAD_STATE_KEY,
                          THREAD_CPU_TIME_KEY,
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -43,10 +43,11 @@
 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.Put;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.thread.dao.ThreadDao;
 import com.redhat.thermostat.thread.model.ThreadInfoData;
 import com.redhat.thermostat.thread.model.ThreadSummary;
@@ -64,37 +65,36 @@
 
     @Override
     public VMThreadCapabilities loadCapabilities(VmRef vm) {
-        try {
-        Query query = storage.createQuery()
-                .from(THREAD_CAPABILITIES)
-                .where(Key.VM_ID, Query.Criteria.EQUALS, vm.getId())
-                .where(Key.AGENT_ID, Query.Criteria.EQUALS, vm.getAgent().getAgentId());
-        
-        VMThreadCapabilities caps = storage.findPojo(query, VMThreadCapabilities.class);
+        Query<VMThreadCapabilities> query = storage.createQuery(THREAD_CAPABILITIES);
+        query.where(Key.VM_ID, Query.Criteria.EQUALS, vm.getId());
+        query.where(Key.AGENT_ID, Query.Criteria.EQUALS, vm.getAgent().getAgentId());
+        query.limit(1);
+        VMThreadCapabilities caps = query.execute().next();
         return caps;
-        } catch (Throwable t) {
-            t.printStackTrace();
-            System.exit(0);
-            return null;
-        }
     }
     
     @Override
     public void saveCapabilities(VMThreadCapabilities caps) {
-        storage.putPojo(THREAD_CAPABILITIES, true, caps);
+        Put replace = storage.createReplace(THREAD_CAPABILITIES);
+        replace.setPojo(caps);
+        replace.apply();
     }
     
     @Override
     public void saveSummary(ThreadSummary summary) {
-        storage.putPojo(THREAD_SUMMARY, false, summary);
+        Put add = storage.createAdd(THREAD_SUMMARY);
+        add.setPojo(summary);
+        add.apply();
     }
     
     @Override
     public ThreadSummary loadLastestSummary(VmRef ref) {
         ThreadSummary summary = null;
 
-        Query query = prepareQuery(THREAD_SUMMARY, ref).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING).limit(1);
-        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class);
+        Query<ThreadSummary> query = prepareQuery(THREAD_SUMMARY, ref);
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        query.limit(1);
+        Cursor<ThreadSummary> cursor = query.execute();
         if (cursor.hasNext()) {
             summary = cursor.next();
         }
@@ -107,10 +107,11 @@
         
         List<ThreadSummary> result = new ArrayList<>();
         
-        Query query = prepareQuery(THREAD_SUMMARY, ref).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        Query<ThreadSummary> query = prepareQuery(THREAD_SUMMARY, ref);
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
 
-        Cursor<ThreadSummary> cursor = storage.findAllPojos(query, ThreadSummary.class);
+        Cursor<ThreadSummary> cursor = query.execute();
         while (cursor.hasNext()) {
             ThreadSummary summary = cursor.next();
             result.add(summary);
@@ -121,18 +122,20 @@
     
     @Override
     public void saveThreadInfo(ThreadInfoData info) {
-        storage.putPojo(THREAD_INFO, false, info);
+        Put add = storage.createAdd(THREAD_INFO);
+        add.setPojo(info);
+        add.apply();
     }
 
     @Override
     public List<ThreadInfoData> loadThreadInfo(VmRef ref, long since) {
         List<ThreadInfoData> result = new ArrayList<>();
         
-        Query query = prepareQuery(THREAD_INFO, ref)
-                .where(Key.TIMESTAMP, Criteria.GREATER_THAN, since)
-                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        Query<ThreadInfoData> query = prepareQuery(THREAD_INFO, ref);
+        query.where(Key.TIMESTAMP, Criteria.GREATER_THAN, since);
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
         
-        Cursor<ThreadInfoData> cursor = storage.findAllPojos(query, ThreadInfoData.class);
+        Cursor<ThreadInfoData> cursor = query.execute();
         while (cursor.hasNext()) {
             ThreadInfoData info = cursor.next();
             result.add(info);
@@ -141,15 +144,14 @@
         return result;
     }
     
-    private Query prepareQuery(Category category, VmRef vm) {
+    private <T extends Pojo> Query<T> prepareQuery(Category<T> category, VmRef vm) {
         return prepareQuery(category, vm.getIdString(), vm.getAgent().getAgentId());
     }
 
-    private Query prepareQuery(Category category, String vmId, String agentId) {
-        Query query = storage.createQuery()
-                .from(category)
-                .where(Key.AGENT_ID, Query.Criteria.EQUALS, agentId)
-                .where(Key.VM_ID, Query.Criteria.EQUALS, Integer.valueOf(vmId));
+    private <T extends Pojo> Query<T> prepareQuery(Category<T> category, String vmId, String agentId) {
+        Query<T> query = storage.createQuery(category);
+        query.where(Key.AGENT_ID, Query.Criteria.EQUALS, agentId);
+        query.where(Key.VM_ID, Query.Criteria.EQUALS, Integer.valueOf(vmId));
         return query;
     }
     
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -38,18 +38,23 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import org.junit.Test;
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+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.Replace;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.thread.dao.ThreadDao;
 import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
@@ -69,9 +74,9 @@
     
     @Test
     public void testLoadVMCapabilities() {
-        MockQuery query = new MockQuery();
+        Query query = mock(Query.class);
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(query);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
         VmRef ref = mock(VmRef.class);
         when(ref.getId()).thenReturn(42);
         
@@ -82,14 +87,20 @@
 
         VMThreadCapabilities expected = new VMThreadCapabilities();
         expected.setSupportedFeaturesList(new String[] { ThreadDao.CPU_TIME, ThreadDao.THREAD_ALLOCATED_MEMORY });
-        when(storage.findPojo(query, VMThreadCapabilities.class)).thenReturn(expected);
+        Cursor cursor = mock(Cursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(expected).thenReturn(null);
+        when(query.execute()).thenReturn(cursor);
         
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
         VMThreadCapabilities caps = dao.loadCapabilities(ref);
 
-        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, 42));
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, "0xcafe"));
-        
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, 42);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "0xcafe");
+        verify(query).limit(1);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
+
         assertFalse(caps.supportContentionMonitor());
         assertTrue(caps.supportCPUTime());
         assertTrue(caps.supportThreadAllocatedMemory());
@@ -98,7 +109,9 @@
     @Test
     public void testSaveVMCapabilities() {
         Storage storage = mock(Storage.class);
-        
+        Replace replace = mock(Replace.class);
+        when(storage.createReplace(any(Category.class))).thenReturn(replace);
+
         VMThreadCapabilities caps = mock(VMThreadCapabilities.class);
         when(caps.supportContentionMonitor()).thenReturn(true);
         when(caps.supportCPUTime()).thenReturn(true);
@@ -107,7 +120,9 @@
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
         dao.saveCapabilities(caps);
 
-        verify(storage).putPojo(ThreadDao.THREAD_CAPABILITIES, true, caps);
+        verify(storage).createReplace(ThreadDao.THREAD_CAPABILITIES);
+        verify(replace).setPojo(caps);
+        verify(replace).apply();
 
     }
 }
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -47,8 +47,8 @@
 
     static final Key<Long> loadedClassesKey = new Key<>("loadedClasses", false);
 
-    static final Category vmClassStatsCategory = new Category(
-            "vm-class-stats", Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, loadedClassesKey);
+    static final Category<VmClassStat> vmClassStatsCategory = new Category<>(
+            "vm-class-stats", VmClassStat.class, Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, loadedClassesKey);
 
     public List<VmClassStat> getLatestClassStats(VmRef ref, long since);
 
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,6 +40,7 @@
 
 import com.redhat.thermostat.common.dao.VmLatestPojoListGetter;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.VmClassStat;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
@@ -52,7 +53,7 @@
     VmClassStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmClassStatsCategory);
-        this.getter = new VmLatestPojoListGetter<>(storage, vmClassStatsCategory, VmClassStat.class);
+        this.getter = new VmLatestPojoListGetter<>(storage, vmClassStatsCategory);
     }
 
     @Override
@@ -62,6 +63,8 @@
 
     @Override
     public void putVmClassStat(VmClassStat stat) {
-        storage.putPojo(vmClassStatsCategory, false, stat);
+        Put add = storage.createAdd(vmClassStatsCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 }
--- a/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,30 +39,27 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
 import java.util.List;
 
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Add;
+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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmClassStat;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
-import com.redhat.thermostat.vm.classstat.common.internal.VmClassStatDAOImpl;
 
 public class VmClassStatDAOTest {
 
@@ -92,13 +89,9 @@
         when(cursor.next()).thenReturn(vmClassStat);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(VmClassStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -110,9 +103,12 @@
         VmClassStatDAO dao = new VmClassStatDAOImpl(storage);
         List<VmClassStat> vmClassStats = dao.getLatestClassStats(vmRef, Long.MIN_VALUE);
 
-        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
-        verify(storage).findAllPojos(arg.capture(), same(VmClassStat.class));
-        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "system");
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, 321);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, vmClassStats.size());
         VmClassStat stat = vmClassStats.get(0);
@@ -129,10 +125,15 @@
     public void testPutVmClassStat() {
 
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+
         VmClassStat stat = new VmClassStat(VM_ID, TIMESTAMP, LOADED_CLASSES);
         VmClassStatDAO dao = new VmClassStatDAOImpl(storage);
         dao.putVmClassStat(stat);
 
-        verify(storage).putPojo(VmClassStatDAO.vmClassStatsCategory, false, stat);
+        verify(storage).createAdd(VmClassStatDAO.vmClassStatsCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
     }
 }
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/VmCpuStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -47,7 +47,7 @@
 
     static final Key<Double> vmCpuLoadKey = new Key<>("cpuLoad", false);
 
-    static final Category vmCpuStatCategory = new Category("vm-cpu-stats",
+    static final Category<VmCpuStat> vmCpuStatCategory = new Category<>("vm-cpu-stats", VmCpuStat.class,
             Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, vmCpuLoadKey);
 
     public abstract List<VmCpuStat> getLatestVmCpuStats(VmRef ref, long since);
--- a/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-cpu/common/src/main/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,6 +40,7 @@
 
 import com.redhat.thermostat.common.dao.VmLatestPojoListGetter;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.VmCpuStat;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
@@ -52,7 +53,7 @@
     VmCpuStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmCpuStatCategory);
-        this.getter = new VmLatestPojoListGetter<>(storage, vmCpuStatCategory, VmCpuStat.class);
+        this.getter = new VmLatestPojoListGetter<>(storage, vmCpuStatCategory);
     }
 
     @Override
@@ -62,6 +63,8 @@
 
     @Override
     public void putVmCpuStat(VmCpuStat stat) {
-        storage.putPojo(vmCpuStatCategory, false, stat);
+        Put add = storage.createAdd(vmCpuStatCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 }
--- a/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-cpu/common/src/test/java/com/redhat/thermostat/vm/cpu/common/internal/VmCpuStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,9 +39,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
@@ -49,19 +49,17 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Add;
+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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmCpuStat;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
 
 public class VmCpuStatDAOTest {
@@ -97,13 +95,9 @@
         when(cursor.next()).thenReturn(cpuStat);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
-        when(storage.findAllPojos(any(Query.class), same(VmCpuStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -116,9 +110,13 @@
         VmCpuStatDAO dao = new VmCpuStatDAOImpl(storage);
         List<VmCpuStat> vmCpuStats = dao.getLatestVmCpuStats(vmRef, Long.MIN_VALUE);
 
-        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
-        verify(storage).findAllPojos(arg.capture(), same(VmCpuStat.class));
-        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(storage).createQuery(VmCpuStatDAO.vmCpuStatCategory);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, vmRef.getAgent().getAgentId());
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, vmRef.getId());
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, vmCpuStats.size());
         VmCpuStat stat = vmCpuStats.get(0);
@@ -130,11 +128,16 @@
     @Test
     public void testPutVmCpuStat() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+        
         VmCpuStat stat = new VmCpuStat(TIMESTAMP, VM_ID, CPU_LOAD);
         VmCpuStatDAO dao = new VmCpuStatDAOImpl(storage);
         dao.putVmCpuStat(stat);
 
-        verify(storage).putPojo(VmCpuStatDAO.vmCpuStatCategory, false, stat);
+        verify(storage).createAdd(VmCpuStatDAO.vmCpuStatCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
 
     }
 }
--- a/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/VmGcStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/VmGcStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,7 +50,7 @@
     /** time in microseconds */
     static final Key<Long> wallTimeKey = new Key<>("wallTime", false);
 
-    static final Category vmGcStatCategory = new Category("vm-gc-stats",
+    static final Category<VmGcStat> vmGcStatCategory = new Category<>("vm-gc-stats", VmGcStat.class,
             Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, collectorKey,
             runCountKey, wallTimeKey);
 
--- a/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -40,6 +40,7 @@
 
 import com.redhat.thermostat.common.dao.VmLatestPojoListGetter;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.model.VmGcStat;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
@@ -52,7 +53,7 @@
     VmGcStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmGcStatCategory);
-        getter = new VmLatestPojoListGetter<>(storage, vmGcStatCategory, VmGcStat.class);
+        getter = new VmLatestPojoListGetter<>(storage, vmGcStatCategory);
     }
 
     @Override
@@ -62,7 +63,9 @@
 
     @Override
     public void putVmGcStat(VmGcStat stat) {
-        storage.putPojo(vmGcStatCategory, false, stat);
+        Put add = storage.createAdd(vmGcStatCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 
 }
--- a/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,28 +39,27 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.Collection;
 import java.util.List;
 
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Add;
+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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmGcStat;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.vm.gc.common.VmGcStatDAO;
-import com.redhat.thermostat.vm.gc.common.internal.VmGcStatDAOImpl;
 
 public class VmGcStatDAOTest {
 
@@ -94,8 +93,9 @@
         when(cursor.next()).thenReturn(vmGcStat);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(new MockQuery());
-        when(storage.findAllPojos(any(Query.class), same(VmGcStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         HostRef hostRef = mock(HostRef.class);
         when(hostRef.getAgentId()).thenReturn("system");
@@ -108,9 +108,14 @@
         VmGcStatDAO dao = new VmGcStatDAOImpl(storage);
         List<VmGcStat> vmGcStats = dao.getLatestVmGcStats(vmRef, Long.MIN_VALUE);
 
-        ArgumentCaptor<MockQuery> arg = ArgumentCaptor.forClass(MockQuery.class);
-        verify(storage).findAllPojos(arg.capture(), same(VmGcStat.class));
-        assertTrue(arg.getValue().hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE));
+        verify(storage).createQuery(VmGcStatDAO.vmGcStatCategory);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, "system");
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, 321);
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, Long.MIN_VALUE);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
 
         assertEquals(1, vmGcStats.size());
         VmGcStat stat = vmGcStats.get(0);
@@ -124,10 +129,15 @@
     @Test
     public void testPutVmGcStat() {
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+        
         VmGcStat stat = new VmGcStat(VM_ID, TIMESTAMP, COLLECTOR, RUN_COUNT, WALL_TIME);
         VmGcStatDAO dao = new VmGcStatDAOImpl(storage);
         dao.putVmGcStat(stat);
 
-        verify(storage).putPojo(VmGcStatDAO.vmGcStatCategory, false, stat);
+        verify(storage).createAdd(VmGcStatDAO.vmGcStatCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
     }
 }
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumperService.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumperService.java	Mon Jan 14 14:04:50 2013 -0500
@@ -41,6 +41,7 @@
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
 import com.redhat.thermostat.client.core.NameMatchingRefFilter;
 import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.utils.OSGIUtils;
 import com.redhat.thermostat.vm.heap.analysis.client.core.internal.HeapDumpController;
@@ -51,12 +52,14 @@
     
     private static final int ORDER = ORDER_MEMORY_GROUP + 60;
     private ApplicationService appService;
+    private VmInfoDAO vmInfoDao;
     private VmMemoryStatDAO vmMemoryStatDao;
     private HeapDAO heapDao;
 
     private Filter<VmRef> filter = new NameMatchingRefFilter<>();
 
-    public HeapDumperService(ApplicationService appService, VmMemoryStatDAO vmMemoryStatDao, HeapDAO heapDao) {
+    public HeapDumperService(ApplicationService appService, VmInfoDAO vmInfoDao, VmMemoryStatDAO vmMemoryStatDao, HeapDAO heapDao) {
+        this.vmInfoDao = vmInfoDao;
         this.vmMemoryStatDao = vmMemoryStatDao;
         this.heapDao = heapDao;
         this.appService = appService;
@@ -69,7 +72,8 @@
         HeapHistogramViewProvider histogramViewProvider = OSGIUtils.getInstance().getService(HeapHistogramViewProvider.class);
         ObjectDetailsViewProvider objectDetailsViewProvider = OSGIUtils.getInstance().getService(ObjectDetailsViewProvider.class);
         ObjectRootsViewProvider objectRootsViewProvider = OSGIUtils.getInstance().getService(ObjectRootsViewProvider.class);
-        return new HeapDumpController(vmMemoryStatDao, heapDao, ref, appService, viewProvider, detailsViewProvider, histogramViewProvider, objectDetailsViewProvider, objectRootsViewProvider);
+        return new HeapDumpController(vmMemoryStatDao, vmInfoDao, heapDao, ref, appService,
+                viewProvider, detailsViewProvider, histogramViewProvider, objectDetailsViewProvider, objectRootsViewProvider);
     }
 
     @Override
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapView.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapView.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,9 +50,14 @@
     public enum HeapDumperAction {
         DUMP_REQUESTED,
         ANALYSE,
-        REQUEST_ABORTED
+        REQUEST_ABORTED,
     }
-   
+
+    public enum DumpDisabledReason {
+        DUMP_IN_PROGRESS,
+        PROCESS_DEAD,
+    }
+
     protected final ActionNotifier<HeapDumperAction> heapDumperNotifier;
     protected HeapView() {
         heapDumperNotifier = new ActionNotifier<HeapDumperAction>(this);
@@ -66,18 +71,21 @@
         heapDumperNotifier.removeActionListener(listener);
     }
 
-    abstract public void updateUsedAndCapacity(String used, String capacity);
+    public abstract void updateUsedAndCapacity(String used, String capacity);
     /** View updates automatically based on the model */
-    abstract public void setModel(OverviewChart model);
-    abstract public void addHeapDump(HeapDump dump);
-    abstract public void clearHeapDumpList();
-    
-    abstract public void openDumpView();
-    abstract public void setChildView(BasicView childView);
+    public abstract void setModel(OverviewChart model);
+
+    public abstract void enableHeapDumping();
+    public abstract void disableHeapDumping(DumpDisabledReason reason);
+
+    public abstract void addHeapDump(HeapDump dump);
+    public abstract void updateHeapDumpList(List<HeapDump> heapDumps);
+    public abstract void clearHeapDumpList();
+
+    public abstract void openDumpView();
+    public abstract void setChildView(BasicView childView);
     public abstract void notifyHeapDumpComplete();
 
-    public abstract void updateHeapDumpList(List<HeapDump> heapDumps);
-
     public abstract void displayWarning(String string);
 
 }
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/Activator.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/Activator.java	Mon Jan 14 14:04:50 2013 -0500
@@ -50,6 +50,7 @@
 import com.redhat.thermostat.common.Constants;
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumperService;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO;
@@ -64,6 +65,7 @@
     public void start(final BundleContext context) throws Exception {
         Class<?>[] deps = new Class<?>[] {
             ApplicationService.class,
+            VmInfoDAO.class,
             VmMemoryStatDAO.class,
             HeapDAO.class,
         };
@@ -74,12 +76,13 @@
             public void dependenciesAvailable(Map<String, Object> services) {
                 ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
                 Objects.requireNonNull(appSvc);
+                VmInfoDAO vmInfoDao = Objects.requireNonNull((VmInfoDAO) services.get(VmInfoDAO.class.getName()));
                 VmMemoryStatDAO vmMemoryStatDao = (VmMemoryStatDAO) services.get(VmMemoryStatDAO.class.getName());
                 Objects.requireNonNull(vmMemoryStatDao);
                 HeapDAO heapDao = (HeapDAO) services.get(HeapDAO.class.getName());
                 Objects.requireNonNull(heapDao);
 
-                HeapDumperService service = new HeapDumperService(appSvc, vmMemoryStatDao, heapDao);
+                HeapDumperService service = new HeapDumperService(appSvc, vmInfoDao, vmMemoryStatDao, heapDao);
                 Dictionary<String, String> properties = new Hashtable<>();
                 properties.put(Constants.GENERIC_SERVICE_CLASSNAME, VmRef.class.getName());
                 reg = context.registerService(InformationService.class.getName(), service , properties);
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java	Mon Jan 14 14:04:50 2013 -0500
@@ -54,6 +54,7 @@
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.locale.Translate;
 import com.redhat.thermostat.storage.model.HeapInfo;
@@ -63,6 +64,7 @@
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.HeapDumperAction;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider;
@@ -77,6 +79,7 @@
 
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
 
+    private final VmInfoDAO vmInfoDao;
     private final VmMemoryStatDAO vmDao;
     private final VmRef ref;
     
@@ -92,19 +95,22 @@
     private ObjectDetailsViewProvider objectDetailsViewProvider;
     private ObjectRootsViewProvider objectRootsViewProvider;
 
+
     public HeapDumpController(final VmMemoryStatDAO vmMemoryStatDao,
+            final VmInfoDAO vmInfoDao,
             final HeapDAO heapDao, final VmRef ref,
             final ApplicationService appService, HeapViewProvider viewProvider,
             HeapDumpDetailsViewProvider detailsViewProvider,
             HeapHistogramViewProvider histogramProvider,
             ObjectDetailsViewProvider objectDetailsProvider,
             ObjectRootsViewProvider objectRootsProvider) {
-        this(vmMemoryStatDao, heapDao, ref, appService, viewProvider,
+        this(vmMemoryStatDao, vmInfoDao, heapDao, ref, appService, viewProvider,
                 detailsViewProvider, histogramProvider, objectDetailsProvider,
                 objectRootsProvider, new HeapDumper(ref));
     }
 
     HeapDumpController(final VmMemoryStatDAO vmMemoryStatDao,
+            final VmInfoDAO vmInfoDao,
             final HeapDAO heapDao, final VmRef ref,
             final ApplicationService appService, HeapViewProvider viewProvider,
             HeapDumpDetailsViewProvider detailsViewProvider,
@@ -118,6 +124,7 @@
         this.detailsViewProvider = detailsViewProvider;
         this.appService = appService;
         this.ref = ref;
+        this.vmInfoDao = vmInfoDao;
         this.vmDao = vmMemoryStatDao;
         this.heapDAO = heapDao;
         
@@ -178,6 +185,7 @@
                 HeapDump dump = null;
                 switch (actionEvent.getActionId()) {
                 case DUMP_REQUESTED:
+                    view.disableHeapDumping(DumpDisabledReason.DUMP_IN_PROGRESS);
                     requestDump(heapDumper);
                     break;
                 case ANALYSE:
@@ -189,6 +197,11 @@
                 }
             }
         });
+
+        boolean vmIsAlive = vmInfoDao.getVmInfo(ref).isAlive();
+        if (!vmIsAlive) {
+            view.disableHeapDumping(DumpDisabledReason.PROCESS_DEAD);
+        }
     }
 
     private void requestDump(final HeapDumper heapDumper) {
@@ -198,6 +211,7 @@
             public void run() {
                 try {
                     heapDumper.dump();
+                    view.enableHeapDumping();
                     view.notifyHeapDumpComplete();
                 } catch (CommandException e) {
                     view.displayWarning(e.getMessage());
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java	Mon Jan 14 14:04:50 2013 -0500
@@ -68,6 +68,10 @@
     HEAP_DUMP_OBJECT_FIND_ROOT,
     
     OBJECT_ROOTS_VIEW_TITLE,
+
+    TRIGGER_HEAP_DUMP,
+    HEAP_DUMP_IN_PROGRESS,
+    PROCESS_EXITED,
     ;
 
     static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.heap.analysis.client.locale.strings";
--- a/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties	Mon Jan 14 14:04:50 2013 -0500
@@ -26,4 +26,8 @@
 HEAP_DUMP_OBJECT_BROWSE_REFERENCES = References
 HEAP_DUMP_OBJECT_FIND_ROOT = Find Root
 
-OBJECT_ROOTS_VIEW_TITLE = Object Roots
\ No newline at end of file
+OBJECT_ROOTS_VIEW_TITLE = Object Roots
+
+TRIGGER_HEAP_DUMP = Heap Dump
+HEAP_DUMP_IN_PROGRESS = dumping...
+PROCESS_EXITED = Process exited.
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ActivatorTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ActivatorTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -45,6 +45,7 @@
 
 import com.redhat.thermostat.client.core.InformationService;
 import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.test.StubBundleContext;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumperService;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO;
@@ -71,10 +72,12 @@
     @Test
     public void verifyActivatorRegistersServices() throws Exception {
         StubBundleContext context = new StubBundleContext();
+        VmInfoDAO vmInfoDao = mock(VmInfoDAO.class);
         VmMemoryStatDAO vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
         HeapDAO heapDAO = mock(HeapDAO.class);
         ApplicationService appSvc = mock(ApplicationService.class);
 
+        context.registerService(VmInfoDAO.class, vmInfoDao, null);
         context.registerService(VmMemoryStatDAO.class, vmMemoryStatDAO, null);
         context.registerService(HeapDAO.class, heapDAO, null);
         context.registerService(ApplicationService.class, appSvc, null);
@@ -88,7 +91,7 @@
         activator.stop(context);
 
         assertEquals(0, context.getServiceListeners().size());
-        assertEquals(3, context.getAllServices().size());
+        assertEquals(4, context.getAllServices().size());
     }
 
 }
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpControllerTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpControllerTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -71,8 +71,10 @@
 import com.redhat.thermostat.common.TimerFactory;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmInfoDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.model.HeapInfo;
+import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.storage.model.VmMemoryStat;
 import com.redhat.thermostat.storage.model.VmMemoryStat.Generation;
 import com.redhat.thermostat.storage.model.VmMemoryStat.Space;
@@ -81,6 +83,7 @@
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.HeapDumperAction;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider;
 import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsView;
@@ -100,6 +103,7 @@
     private Timer timer;
 
     private HeapDAO heapDao;
+    private VmInfoDAO vmInfoDao;
     private VmMemoryStatDAO vmDao;
     private HeapView view;
     private HeapDumpDetailsView detailsView;
@@ -120,6 +124,10 @@
     public void setUp() {
         heapDao = mock(HeapDAO.class);
         vmDao = mock(VmMemoryStatDAO.class);
+        vmInfoDao = mock(VmInfoDAO.class);
+        VmInfo vmInfo = mock(VmInfo.class);
+        when(vmInfo.isAlive()).thenReturn(true);
+        when(vmInfoDao.getVmInfo(isA(VmRef.class))).thenReturn(vmInfo);
         appService = mock(ApplicationService.class);
         heapDumper = mock(HeapDumper.class);
 
@@ -184,7 +192,7 @@
         when(ref.getIdString()).thenReturn("vm-id");
         when(ref.getAgent()).thenReturn(hostRef);
 
-        controller = new HeapDumpController(vmDao, heapDao, ref, appService,
+        controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService,
                 viewProvider, detailsViewProvider, histogramProvider,
                 objectDetailsProvider, objectRootsProvider, heapDumper);
     }
@@ -268,7 +276,7 @@
         when(cache.getAttribute(any(VmRef.class))).thenReturn(dump);
         when(appService.getApplicationCache()).thenReturn(cache);
         VmRef ref = mock(VmRef.class);
-        controller = new HeapDumpController(vmDao, heapDao, ref, appService,
+        controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService,
                 viewProvider, detailsViewProvider, histogramProvider,
                 objectDetailsProvider, objectRootsProvider, heapDumper);
         
@@ -293,7 +301,7 @@
 
         when(appService.getApplicationCache()).thenReturn(cache);
         VmRef ref = mock(VmRef.class);
-        controller = new HeapDumpController(vmDao, heapDao, ref, appService,
+        controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService,
                 viewProvider, detailsViewProvider, histogramProvider,
                 objectDetailsProvider, objectRootsProvider, heapDumper);
         
@@ -301,6 +309,26 @@
     }
 
     @Test
+    public void testHeapDumpingDisabledForDeadVms() {
+        ApplicationCache cache = mock(ApplicationCache.class);
+        when(appService.getApplicationCache()).thenReturn(cache);
+        setUpTimers();
+
+        VmRef ref = mock(VmRef.class);
+
+        VmInfo vmInfo = mock(VmInfo.class);
+        when(vmInfo.isAlive()).thenReturn(false);
+
+        when(vmInfoDao.getVmInfo(ref)).thenReturn(vmInfo);
+
+        controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService,
+                viewProvider, detailsViewProvider, histogramProvider,
+                objectDetailsProvider, objectRootsProvider, heapDumper);
+
+        verify(view).disableHeapDumping(DumpDisabledReason.PROCESS_DEAD);
+    }
+
+    @Test
     public void testRequestHeapDump() throws CommandException, InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         mockExecutorService(latch);
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingView.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapSwingView.java	Mon Jan 14 14:04:50 2013 -0500
@@ -77,9 +77,6 @@
         stats.addHeapDumperListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
-                
-                stats.disableHeapDumperControl();
-                
                 heapDumperNotifier.fireAction(HeapDumperAction.DUMP_REQUESTED);
             }
         });
@@ -156,6 +153,26 @@
     }
 
     @Override
+    public void enableHeapDumping() {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                stats.enableHeapDumperControl();
+            }
+        });
+    }
+
+    @Override
+    public void disableHeapDumping(final DumpDisabledReason reason) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                stats.disableHeapDumperControl(reason);
+            }
+        });
+    }
+
+    @Override
     public void addHeapDump(final HeapDump dump) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
@@ -211,12 +228,7 @@
 
     @Override
     public void notifyHeapDumpComplete() {
-        EventQueue.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                stats.enableHeapDumperControl();
-            }
-        });
+        enableHeapDumping();
     }
 
     @Override
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/StatsPanel.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/StatsPanel.java	Mon Jan 14 14:04:50 2013 -0500
@@ -53,11 +53,16 @@
 import javax.swing.SwingConstants;
 import javax.swing.event.ListSelectionListener;
 
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason;
+import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDump;
 
 @SuppressWarnings("serial")
 public class StatsPanel extends JPanel {
     
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
     private JPanel leftPanel;
     
     private JButton heapDumpButton;
@@ -99,7 +104,7 @@
         max = new JLabel("-");
         max.setHorizontalAlignment(SwingConstants.RIGHT);
         
-        heapDumpButton = new JButton("Heap Dump");
+        heapDumpButton = new JButton(translator.localize(LocaleResources.TRIGGER_HEAP_DUMP));
         heapDumpButton.setName("heapDumpButton");
 
         JScrollPane dumpListScrollPane = new JScrollPane();
@@ -173,13 +178,22 @@
         dumpList.addListSelectionListener(listener);
     }
     
-    public void disableHeapDumperControl() {
-        heapDumpButton.setText("dumping...");
+    public void disableHeapDumperControl(DumpDisabledReason reason) {
+        String text = null;
+        switch (reason) {
+        case DUMP_IN_PROGRESS:
+            text = translator.localize(LocaleResources.HEAP_DUMP_IN_PROGRESS);
+            break;
+        case PROCESS_DEAD:
+            text = translator.localize(LocaleResources.PROCESS_EXITED);
+            break;
+        }
+        heapDumpButton.setText(text);
         heapDumpButton.setEnabled(false);
     }
 
     public void enableHeapDumperControl() {
-        heapDumpButton.setText("Heap Dump");
+        heapDumpButton.setText(translator.localize(LocaleResources.TRIGGER_HEAP_DUMP));
         heapDumpButton.setEnabled(true);
     }
 
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -52,7 +52,7 @@
     static final Key<String> heapDumpIdKey = new Key<String>("heapDumpId", false);
     static final Key<String> histogramIdKey = new Key<String>("histogramId", false);
 
-    public static final Category heapInfoCategory = new Category("vm-heap-info", Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, heapIdKey, heapDumpIdKey, histogramIdKey);
+    public static final Category<HeapInfo> heapInfoCategory = new Category<>("vm-heap-info", HeapInfo.class, Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, heapIdKey, heapDumpIdKey, histogramIdKey);
 
     void putHeapInfo(HeapInfo heapInfo, File heapDumpFile, ObjectHistogram histogramData) throws IOException;
 
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -53,6 +53,7 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
@@ -86,7 +87,10 @@
         if (histogramData != null) {
             heapInfo.setHistogramId(histogramId);
         }
-        storage.putPojo(heapInfoCategory, false, heapInfo);
+        Put add = storage.createAdd(heapInfoCategory);
+        add.setPojo(heapInfo);
+        add.apply();
+
         if (heapDumpData != null) {
             storage.saveFile(heapDumpId, new FileInputStream(heapDumpData));
         }
@@ -106,11 +110,10 @@
 
     @Override
     public Collection<HeapInfo> getAllHeapInfo(VmRef vm) {
-        Query query = storage.createQuery()
-                .from(heapInfoCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, vm.getAgent().getAgentId())
-                .where(Key.VM_ID, Criteria.EQUALS, vm.getId());
-        Cursor<HeapInfo> cursor = storage.findAllPojos(query, HeapInfo.class);
+        Query<HeapInfo> query = storage.createQuery(heapInfoCategory);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, vm.getAgent().getAgentId());
+        query.where(Key.VM_ID, Criteria.EQUALS, vm.getId());
+        Cursor<HeapInfo> cursor = query.execute();
         Collection<HeapInfo> heapInfos = new ArrayList<>();
         while (cursor.hasNext()) {
             heapInfos.add(cursor.next());
@@ -137,12 +140,12 @@
 
     @Override
     public HeapInfo getHeapInfo(String heapId) {
-        Query query = storage.createQuery()
-                .from(heapInfoCategory)
-                .where(heapIdKey, Criteria.EQUALS, heapId);
+        Query<HeapInfo> query = storage.createQuery(heapInfoCategory);
+        query.where(heapIdKey, Criteria.EQUALS, heapId);
+        query.limit(1);
         HeapInfo found = null;
         try {
-            found = storage.findPojo(query, HeapInfo.class);
+            found = query.execute().next();
         } catch (IllegalArgumentException iae) {
             /*
              * if the heap id is not found, we get a nice
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/internal/HeapDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,7 +42,6 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -61,19 +60,16 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Add;
 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.Storage;
 import com.redhat.thermostat.storage.model.HeapInfo;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO;
 import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord;
 import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram;
@@ -85,21 +81,23 @@
 
     private HeapDAO dao;
     private Storage storage;
+    private Add add;
     private HeapInfo heapInfo;
     private File heapDumpData;
     private ObjectHistogram histogram;
     private InputStream histogramData;
 
+    private Query query;
+
     @Before
     public void setUp() throws IOException {
         storage = mock(Storage.class);
+        add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+
         when(storage.getAgentId()).thenReturn("test");
-        when(storage.createQuery()).then(new Answer<Query>() {
-            @Override
-            public Query answer(InvocationOnMock invocation) throws Throwable {
-                return new MockQuery();
-            }
-        });
+        query = mock(Query.class); 
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
 
         dao = new HeapDAOImpl(storage);
         
@@ -111,12 +109,6 @@
         out.close();
         histogramData = createHistogramData();
 
-        // Setup for reading data from DB.
-        MockQuery findAllQuery = new MockQuery()
-            .from(HeapDAO.heapInfoCategory)
-            .where(Key.AGENT_ID, Criteria.EQUALS, "123")
-            .where(Key.VM_ID, Criteria.EQUALS, 234);
-
         @SuppressWarnings("unchecked")
         Cursor<HeapInfo> cursor = mock(Cursor.class);
         HeapInfo info1 = new HeapInfo(234, 12345L);
@@ -131,7 +123,7 @@
 
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(info1).thenReturn(info2).thenReturn(null);
-        when(storage.findAllPojos(findAllQuery, HeapInfo.class)).thenReturn(cursor);
+        when(query.execute()).thenReturn(cursor);
 
         // Setup for reading heapdump data.
         when(storage.loadFile("test-heap")).thenReturn(new ByteArrayInputStream(data));
@@ -170,6 +162,7 @@
 
     @After
     public void tearDown() {
+        query = null;
         histogramData = null;
         histogram = null;
         heapDumpData.delete();
@@ -177,6 +170,7 @@
         heapInfo = null;
         dao = null;
         storage = null;
+        add = null;
     }
 
     @Test
@@ -198,7 +192,9 @@
     public void testPutHeapInfo() throws IOException {
         dao.putHeapInfo(heapInfo, heapDumpData, histogram);
 
-        verify(storage).putPojo(HeapDAO.heapInfoCategory, false, heapInfo);
+        verify(storage).createAdd(HeapDAO.heapInfoCategory);
+        verify(add).setPojo(heapInfo);
+        verify(add).apply();
 
         ArgumentCaptor<InputStream> data = ArgumentCaptor.forClass(InputStream.class);
         verify(storage).saveFile(eq("heapdump-test-123-12345"), data.capture());
@@ -224,7 +220,9 @@
     public void testPutHeapInfoWithoutDump() throws IOException {
         dao.putHeapInfo(heapInfo, null, null);
 
-        verify(storage).putPojo(HeapDAO.heapInfoCategory, false, heapInfo);
+        verify(storage).createAdd(HeapDAO.heapInfoCategory);
+        verify(add).setPojo(heapInfo);
+        verify(add).apply();
 
         verify(storage, never()).saveFile(anyString(), any(InputStream.class));
         assertEquals("test-123-12345", heapInfo.getHeapId());
@@ -276,9 +274,7 @@
 
     @Test
     public void testInvalidHeapId() throws IOException {
-        storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(new MockQuery());
-        when(storage.findPojo(any(Query.class), same(HeapInfo.class))).thenThrow(new IllegalArgumentException("invalid ObjectId"));
+        when(query.execute()).thenThrow(new IllegalArgumentException("invalid ObjectId"));
         dao = new HeapDAOImpl(storage);
         heapInfo = dao.getHeapInfo("some-random-heap-id");
         assertTrue(heapInfo == null);
--- a/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/VmMemoryStatDAO.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/VmMemoryStatDAO.java	Mon Jan 14 14:04:50 2013 -0500
@@ -48,7 +48,7 @@
 
     static final Key<Generation[]> generationsKey = new Key<>("generations", false);
 
-    static final Category vmMemoryStatsCategory = new Category("vm-memory-stats",
+    static final Category<VmMemoryStat> vmMemoryStatsCategory = new Category<>("vm-memory-stats", VmMemoryStat.class,
             Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, generationsKey);
 
     public VmMemoryStat getLatestMemoryStat(VmRef ref);
--- a/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImpl.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-memory/common/src/main/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOImpl.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,9 +42,10 @@
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.model.VmMemoryStat;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
 
@@ -56,18 +57,17 @@
     VmMemoryStatDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(vmMemoryStatsCategory);
-        getter = new VmLatestPojoListGetter<>(storage, vmMemoryStatsCategory, VmMemoryStat.class);
+        getter = new VmLatestPojoListGetter<>(storage, vmMemoryStatsCategory);
     }
 
     @Override
     public VmMemoryStat getLatestMemoryStat(VmRef ref) {
-        Query query = storage.createQuery()
-                .from(vmMemoryStatsCategory)
-                .where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId())
-                .where(Key.VM_ID, Criteria.EQUALS, ref.getId())
-                .sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING)
-                .limit(1);
-        Cursor<VmMemoryStat> cursor = storage.findAllPojos(query, VmMemoryStat.class);
+        Query<VmMemoryStat> query = storage.createQuery(vmMemoryStatsCategory);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgent().getAgentId());
+        query.where(Key.VM_ID, Criteria.EQUALS, ref.getId());
+        query.sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
+        query.limit(1);
+        Cursor<VmMemoryStat> cursor = query.execute();
         if (cursor.hasNext()) {
             return cursor.next();
         }
@@ -76,7 +76,9 @@
 
     @Override
     public void putVmMemoryStat(VmMemoryStat stat) {
-        storage.putPojo(vmMemoryStatsCategory, false, stat);
+        Put add = storage.createAdd(vmMemoryStatsCategory);
+        add.setPojo(stat);
+        add.apply();
     }
 
     @Override
--- a/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/vm-memory/common/src/test/java/com/redhat/thermostat/vm/memory/common/internal/VmMemoryStatDAOTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,9 +39,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
@@ -54,17 +54,17 @@
 
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.storage.core.Add;
+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.Storage;
-import com.redhat.thermostat.storage.core.Query.Criteria;
 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.test.MockQuery;
 import com.redhat.thermostat.vm.memory.common.VmMemoryStatDAO;
-import com.redhat.thermostat.vm.memory.common.internal.VmMemoryStatDAOImpl;
 
 public class VmMemoryStatDAOTest {
 
@@ -74,7 +74,7 @@
     private Storage storage;
     private VmRef vmRef;
 
-    private MockQuery query;
+    private Query query;
     private Cursor<VmMemoryStat> cursor;
 
     @SuppressWarnings("unchecked")
@@ -90,11 +90,11 @@
         when(vmRef.getId()).thenReturn(VM_ID);
 
         storage = mock(Storage.class);
-        query = new MockQuery();
-        when(storage.createQuery()).thenReturn(query);
+        query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
 
         cursor = mock(Cursor.class);
-        when(storage.findAllPojos(any(Query.class), same(VmMemoryStat.class))).thenReturn(cursor);
+        when(query.execute()).thenReturn(cursor);
 
         when(cursor.hasNext()).thenReturn(false);
 
@@ -136,14 +136,16 @@
 
         verifyQuery();
 
-        assertTrue(query.hasWhereClause(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l));
+        verify(query).where(Key.TIMESTAMP, Criteria.GREATER_THAN, 123l);
+        verify(query).execute();
+        verifyNoMoreInteractions(query);
     }
 
     private void verifyQuery() {
 
-        assertTrue(query.hasWhereClause(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID));
-        assertTrue(query.hasWhereClause(Key.VM_ID, Criteria.EQUALS, VM_ID));
-        assertTrue(query.hasSort(Key.TIMESTAMP, Query.SortDirection.DESCENDING));
+        verify(query).where(Key.AGENT_ID, Criteria.EQUALS, AGENT_ID);
+        verify(query).where(Key.VM_ID, Criteria.EQUALS, VM_ID);
+        verify(query).sort(Key.TIMESTAMP, Query.SortDirection.DESCENDING);
     }
 
     @Test
@@ -152,8 +154,9 @@
         when(cursor.next()).thenReturn(null);
 
         Storage storage = mock(Storage.class);
-        when(storage.createQuery()).thenReturn(new MockQuery());
-        when(storage.findAllPojos(any(Query.class), same(VmMemoryStat.class))).thenReturn(cursor);
+        Query query = mock(Query.class);
+        when(storage.createQuery(any(Category.class))).thenReturn(query);
+        when(query.execute()).thenReturn(cursor);
 
         VmMemoryStatDAO impl = new VmMemoryStatDAOImpl(storage);
         VmMemoryStat latest = impl.getLatestMemoryStat(vmRef);
@@ -194,9 +197,14 @@
         VmMemoryStat stat = new VmMemoryStat(1, 2, generations.toArray(new Generation[generations.size()]));
 
         Storage storage = mock(Storage.class);
+        Add add = mock(Add.class);
+        when(storage.createAdd(any(Category.class))).thenReturn(add);
+        
         VmMemoryStatDAO dao = new VmMemoryStatDAOImpl(storage);
         dao.putVmMemoryStat(stat);
 
-        verify(storage).putPojo(VmMemoryStatDAO.vmMemoryStatsCategory, false, stat);
+        verify(storage).createAdd(VmMemoryStatDAO.vmMemoryStatsCategory);
+        verify(add).setPojo(stat);
+        verify(add).apply();
     }
 }
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Mon Jan 14 14:04:50 2013 -0500
@@ -43,6 +43,7 @@
 import java.io.OutputStream;
 import java.io.Reader;
 import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.SecureRandom;
@@ -57,6 +58,7 @@
 
 import javax.net.ssl.SSLContext;
 
+import org.apache.commons.beanutils.BeanUtils;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
@@ -84,17 +86,20 @@
 import com.redhat.thermostat.common.ssl.SslInitException;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.AuthToken;
+import com.redhat.thermostat.storage.core.BasePut;
 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.Key;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
 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;
@@ -271,10 +276,53 @@
 
     }
 
+    private class WebAdd extends BasePut implements Add {
+
+        @Override
+        public void apply() {
+            int categoryId = getCategoryId(getCategory());
+            putImpl(new WebInsert(categoryId, false), getPojo());
+        }
+        
+    }
+
+    private class WebReplace extends BasePut implements Replace {
+
+        @Override
+        public void apply() {
+            int categoryId = getCategoryId(getCategory());
+            putImpl(new WebInsert(categoryId, true), getPojo());
+        }
+        
+    }
+
+    private class WebUpdateImpl extends WebUpdate implements Update {
+    
+        @Override
+        public void apply() {
+            updatePojo(this);
+        }
+    }
+
+    private class WebQueryImpl<T extends Pojo> extends WebQuery<T> {
+
+        private transient Class<T> dataClass;
+
+        WebQueryImpl(int categoryId, Class<T> dataClass) {
+            super(categoryId);
+            this.dataClass = dataClass;
+        }
+
+        @Override
+        public Cursor<T> execute() {
+            return findAllPojos(this, dataClass);
+        }
+    }
+
     private String endpoint;
     private UUID agentId;
 
-    private Map<Category, Integer> categoryIds;
+    private Map<Category<?>, Integer> categoryIds;
     private Gson gson;
     // package private for testing
     DefaultHttpClient httpClient;
@@ -384,7 +432,7 @@
     }
 
     @Override
-    public void registerCategory(Category category) throws StorageException {
+    public void registerCategory(Category<?> category) throws StorageException {
         NameValuePair nameParam = new BasicNameValuePair("name",
                 category.getName());
         NameValuePair categoryParam = new BasicNameValuePair("category",
@@ -400,8 +448,8 @@
     }
 
     @Override
-    public Query createQuery() {
-        return new WebQuery(categoryIds);
+    public <T extends Pojo> Query<T> createQuery(Category<T> category) {
+        return new WebQueryImpl<>(categoryIds.get(category), category.getDataClass());
     }
 
     @Override
@@ -410,43 +458,24 @@
     }
 
     @Override
-    public WebUpdate createUpdate() {
-        return new WebUpdate(categoryIds);
+    public Update createUpdate(Category<?> category) {
+        WebUpdateImpl updateImpl = new WebUpdateImpl();
+        updateImpl.setCategoryId(categoryIds.get(category));
+        return updateImpl;
     }
 
     @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));
+    private <T extends Pojo> Cursor<T> findAllPojos(WebQuery<T> query, Class<T> resultClass) throws StorageException {
+        NameValuePair queryParam = new BasicNameValuePair("query", gson.toJson(query));
         List<NameValuePair> formparams = Arrays.asList(queryParam);
-        try (CloseableHttpEntity entity = post(endpoint + "/find-all",
-                formparams)) {
+        try (CloseableHttpEntity entity = post(endpoint + "/find-all", formparams)) {
             Reader reader = getContentAsReader(entity);
-            T[] result = (T[]) gson.fromJson(reader,
-                    Array.newInstance(resultClass, 0).getClass());
+            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();
     }
@@ -457,12 +486,10 @@
     }
 
     @Override
-    public long getCount(Category category) throws StorageException {
-        NameValuePair categoryParam = new BasicNameValuePair("category",
-                gson.toJson(categoryIds.get(category)));
+    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)) {
+        try (CloseableHttpEntity entity = post(endpoint + "/get-count", formparams)) {
             Reader reader = getContentAsReader(entity);
             long result = gson.fromJson(reader, Long.class);
             return result;
@@ -483,18 +510,22 @@
     }
 
     @Override
-    public void putPojo(final Category category, final boolean replace, final AgentIdPojo pojo)
-            throws StorageException {
+    public Add createAdd(Category<?> into) {
+        WebAdd add = new WebAdd();
+        add.setCategory(into);
+        return add;
+    }
 
-        // TODO: This logic should probably be moved elsewhere. I.e. out of the
-        // Storage API.
-        if (pojo.getAgentId() == null) {
-            pojo.setAgentId(getAgentId());
-        }
+    @Override
+    public Replace createReplace(Category<?> into) {
+        WebReplace replace = new WebReplace();
+        replace.setCategory(into);
+        return replace;
+    }
 
-        int categoryId = categoryIds.get(category);
-        WebInsert insert = new WebInsert(categoryId, replace, pojo.getClass()
-                .getName());
+    private void putImpl(WebInsert insert, final Pojo pojo) throws StorageException {
+
+        maybeAddAgentId(pojo);
         NameValuePair insertParam = new BasicNameValuePair("insert",
                 gson.toJson(insert));
         NameValuePair pojoParam = new BasicNameValuePair("pojo",
@@ -504,6 +535,16 @@
 
     }
 
+    private void maybeAddAgentId(final Pojo pojo) throws AssertionError {
+        try {
+            if (BeanUtils.getProperty(pojo, Key.AGENT_ID.getName()) == null) {
+                BeanUtils.setProperty(pojo, Key.AGENT_ID.getName(), getAgentId());
+            }
+        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+            throw new AssertionError("Pojo needs to have an agentId property");
+        }
+    }
+
     @Override
     public void removePojo(Remove remove) throws StorageException {
         NameValuePair removeParam = new BasicNameValuePair("remove",
@@ -525,8 +566,7 @@
         this.agentId = agentId;
     }
 
-    @Override
-    public void updatePojo(Update update) throws StorageException {
+    private void updatePojo(Update update) throws StorageException {
         WebUpdate webUp = (WebUpdate) update;
         List<WebUpdate.UpdateValue> updateValues = webUp.getUpdates();
         List<Object> values = new ArrayList<>(updateValues.size());
@@ -599,4 +639,8 @@
         // Nothing to do here.
     }
 
+    int getCategoryId(Category<?> category) {
+        return categoryIds.get(category);
+    }
+
 }
--- a/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/client/src/test/java/com/redhat/thermostat/web/client/internal/WebStorageTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -86,9 +86,11 @@
 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.Put;
 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.storage.core.Update;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
 import com.redhat.thermostat.web.common.Qualifier;
@@ -111,7 +113,7 @@
     private String requestURI;
     private int responseStatus;
 
-    private static Category category;
+    private static Category<TestObj> category;
     private static Key<String> key1;
     private static Key<Integer> key2;
 
@@ -121,7 +123,7 @@
     public static void setupCategory() {
         key1 = new Key<>("property1", true);
         key2 = new Key<>("property2", true);
-        category = new Category("test", key1);
+        category = new Category<>("test", TestObj.class, key1);
     }
 
     @AfterClass
@@ -219,35 +221,6 @@
     }
 
     @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();
@@ -258,15 +231,16 @@
         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");
+        Query<TestObj> query = storage.createQuery(category);
+        query.where(key1, Criteria.EQUALS, "fluff");
 
-        Cursor<TestObj> results = storage.findAllPojos(query, TestObj.class);
+        Cursor<TestObj> results = query.execute();
         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);
+        WebQuery<?> restQuery = gson.fromJson(parts[1], WebQuery.class);
 
         assertEquals(42, restQuery.getCategoryId());
         List<Qualifier<?>> qualifiers = restQuery.getQualifiers();
@@ -289,7 +263,13 @@
         TestObj obj = new TestObj();
         obj.setProperty1("fluff");
 
-        storage.putPojo(category, true, obj);
+        // We need an agentId, so that we can check automatic insert of agentId.
+        UUID agentId = new UUID(1, 2);
+        storage.setAgentId(agentId);
+
+        Put replace = storage.createReplace(category);
+        replace.setPojo(obj);
+        replace.apply();
 
         Gson gson = new Gson();
         StringReader reader = new StringReader(requestBody);
@@ -302,12 +282,14 @@
         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()));
+        Object resultObj = gson.fromJson(parts[1], TestObj.class);
+
+        // Set agentId on expected object, because we expect WebStorage to insert it for us.
+        obj.setAgentId(agentId.toString());
         assertEquals(obj, resultObj);
     }
 
@@ -352,21 +334,19 @@
 
     @Test
     public void testCreateUpdate() {
-        WebUpdate update = (WebUpdate) storage.createUpdate();
+        WebUpdate update = (WebUpdate) storage.createUpdate(category);
         assertNotNull(update);
-        update = update.from(category);
         assertEquals(42, update.getCategoryId());
-        assertNotNull(update);
-        update = update.where(key1, "test");
-        assertNotNull(update);
+
+        update.where(key1, "test");
         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);
+
+        update.set(key1, "fluff");
         List<WebUpdate.UpdateValue> updates = update.getUpdates();
         assertEquals(1, updates.size());
         assertEquals("fluff", updates.get(0).getValue());
@@ -377,8 +357,11 @@
     @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);
+        Update update = storage.createUpdate(category);
+        update.where(key1, "test");
+        update.set(key1, "fluff");
+        update.set(key2, 42);
+        update.apply();
 
         Gson gson = new Gson();
         StringReader reader = new StringReader(requestBody);
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebInsert.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebInsert.java	Mon Jan 14 14:04:50 2013 -0500
@@ -42,16 +42,13 @@
 
     private int categoryId;
     private boolean replace;
-    private String pojoClass;
 
     public WebInsert() {
-        this(-1, false, null);
     }
 
-    public WebInsert(int categoryId, boolean replace, String pojoClass) {
+    public WebInsert(int categoryId, boolean replace) {
         this.categoryId = categoryId;
         this.replace = replace;
-        this.pojoClass = pojoClass;
     }
 
     public int getCategoryId() {
@@ -62,14 +59,6 @@
         this.categoryId = categoryId;
     }
 
-    public String getPojoClass() {
-        return pojoClass;
-    }
-
-    public void setPojoClass(String pojoClass) {
-        this.pojoClass = pojoClass;
-    }
-
     public boolean isReplace() {
         return replace;
     }
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebQuery.java	Mon Jan 14 14:04:50 2013 -0500
@@ -39,35 +39,25 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 import com.redhat.thermostat.storage.core.AbstractQuery;
-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.model.Pojo;
 
-public class WebQuery extends AbstractQuery {
+public class WebQuery<T extends Pojo> extends AbstractQuery<T> {
 
     private List<Qualifier<?>> qualifiers;
-    private String resultClassName;
-
-    private transient Map<Category, Integer> categoryIdMap;
 
     private int categoryId;
 
     public WebQuery() {
-        this(null);
+        this(-1);
     }
 
-    public WebQuery(Map<Category, Integer> categoryIdMap) {
+    public WebQuery(int categoryId) {
         qualifiers = new ArrayList<>();
-        this.categoryIdMap = categoryIdMap;
-    }
-
-    @Override
-    public WebQuery from(Category category) {
-        categoryId = categoryIdMap.get(category);
-        return this;
+        this.categoryId = categoryId;
     }
 
     public int getCategoryId() {
@@ -79,9 +69,8 @@
     }
 
     @Override
-    public <T> WebQuery where(Key<T> key, Criteria criteria, T value) {
+    public <S> void where(Key<S> key, Criteria criteria, S value) {
         qualifiers.add(new Qualifier<>(key, criteria, value));
-        return this;
     }
 
     public List<Qualifier<?>> getQualifiers() {
@@ -92,12 +81,10 @@
         this.qualifiers = qualifiers;
     }
 
-    public String getResultClassName() {
-        return resultClassName;
-    }
-
-    public void setResultClassName(String resultClassName) {
-        this.resultClassName = resultClassName;
+    @Override
+    public Cursor<T> execute() {
+        // This should only ever be called when created from WebStorage, which provides its own subclass.
+        throw new IllegalStateException();
     }
 
 }
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebRemove.java	Mon Jan 14 14:04:50 2013 -0500
@@ -47,7 +47,7 @@
 
 public class WebRemove implements Remove {
 
-    private transient Map<Category, Integer> categoryIds;
+    private transient Map<Category<?>, Integer> categoryIds;
     private int categoryId;
     private List<Qualifier<?>> qualifiers;
 
@@ -56,7 +56,7 @@
         this(null);
     }
 
-    public WebRemove(Map<Category, Integer> categoryIds) {
+    public WebRemove(Map<Category<?>, Integer> categoryIds) {
         qualifiers = new ArrayList<>();
         this.categoryIds = categoryIds;
     }
--- a/web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/common/src/main/java/com/redhat/thermostat/web/common/WebUpdate.java	Mon Jan 14 14:04:50 2013 -0500
@@ -38,14 +38,11 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
-import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.core.Query.Criteria;
 
-public class WebUpdate implements Update {
+public class WebUpdate {
 
     public static class UpdateValue {
         private Key<?> key;
@@ -90,44 +87,31 @@
 
     }
 
-    private transient Map<Category, Integer> categoryIds;
-    private Integer categoryId;
+    private int categoryId;
     private List<Qualifier<?>> qualifiers;
     private List<UpdateValue> updateValues;
 
-    // NOTE: This is needed for de-serialization!
     public WebUpdate() {
-        this(null);
-    }
-
-    public WebUpdate(Map<Category, Integer> categoryIds) {
         qualifiers = new ArrayList<>();
         updateValues = new ArrayList<>();
-        this.categoryIds = categoryIds;
     }
 
-    @Override
-    public WebUpdate from(Category category) {
-        categoryId = categoryIds.get(category);
-        return this;
+    public <T> void where(Key<T> key, T value) {
+        qualifiers.add(new Qualifier<T>(key, Criteria.EQUALS, value));
     }
 
-    @Override
-    public <T> WebUpdate where(Key<T> key, T value) {
-        qualifiers.add(new Qualifier<T>(key, Criteria.EQUALS, value));
-        return this;
-    }
-
-    @Override
-    public <T> WebUpdate set(Key<T> key, T value) {
+    public <T> void set(Key<T> key, T value) {
         updateValues.add(new UpdateValue(key, value));
-        return this;
     }
 
     public int getCategoryId() {
         return categoryId;
     }
 
+    public void setCategoryId(int categoryId) {
+        this.categoryId = categoryId;
+    }
+
     public List<Qualifier<?>> getQualifiers() {
         return qualifiers;
     }
--- a/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/common/src/test/java/com/redhat/thermostat/web/common/WebQueryTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -36,7 +36,6 @@
 
 package com.redhat.thermostat.web.common;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
 
 import java.util.HashMap;
 import java.util.List;
@@ -47,19 +46,22 @@
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query.Criteria;
-import com.redhat.thermostat.web.common.Qualifier;
-import com.redhat.thermostat.web.common.WebQuery;
+import com.redhat.thermostat.storage.model.Pojo;
 
 public class WebQueryTest {
 
+    private static class TestObj implements Pojo {
+        
+    }
+
     @Test
     public void test() {
         Key<String> key1 = new Key<>("testkey", true);
-        Category category = new Category("test", key1);
+        Category<TestObj> category = new Category<>("test", TestObj.class, key1);
         Map<Category,Integer> categoryIdMap = new HashMap<>();
         categoryIdMap.put(category, 42);
-        WebQuery query = new WebQuery(categoryIdMap);
-        query.from(category).where(key1, Criteria.EQUALS, "fluff");
+        WebQuery query = new WebQuery(42);
+        query.where(key1, Criteria.EQUALS, "fluff");
 
         List<Qualifier<?>> qualifiers = query.getQualifiers();
         assertEquals(1, qualifiers.size());
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Mon Jan 14 14:04:50 2013 -0500
@@ -65,12 +65,12 @@
 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.Put;
 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.storage.core.Storage;
 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.Qualifier;
 import com.redhat.thermostat.web.common.StorageWrapper;
@@ -100,7 +100,7 @@
     private int currentCategoryId;
 
     private Map<String, Integer> categoryIds;
-    private Map<Integer, Category> categories;
+    private Map<Integer, Category<?>> categories;
 
     public void init() {
         gson = new GsonBuilder().registerTypeHierarchyAdapter(Pojo.class, new ThermostatGSONConverter()).create();
@@ -126,9 +126,7 @@
         String uri = req.getRequestURI();
         int lastPartIdx = uri.lastIndexOf("/");
         String cmd = uri.substring(lastPartIdx + 1);
-        if (cmd.equals("find-pojo")) {
-            findPojo(req, resp);
-        } else if (cmd.equals("find-all")) {
+        if (cmd.equals("find-all")) {
             findAll(req, resp);
         } else if (cmd.equals("put-pojo")) {
             putPojo(req, resp);
@@ -207,7 +205,7 @@
         try {
             String categoryParam = req.getParameter("category");
             int categoryId = gson.fromJson(categoryParam, Integer.class);
-            Category category = categories.get(categoryId);
+            Category<?> category = getCategoryFromId(categoryId);
             long result = storage.getCount(category);
             resp.setStatus(HttpServletResponse.SC_OK);
             resp.setContentType("application/json");
@@ -226,8 +224,8 @@
         if (categoryIds.containsKey(categoryName)) {
             id = categoryIds.get(categoryName);
         } else {
-            // The following has the side effect of registering the newly deserialized Category in the Categories clas.
-            Category category = gson.fromJson(categoryParam, Category.class);
+            // The following has the side effect of registering the newly deserialized Category in the Categories class.
+            Category<?> category = gson.fromJson(categoryParam, Category.class);
             storage.registerCategory(category);
 
             id = currentCategoryId;
@@ -247,19 +245,18 @@
             resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
             return;
         }
-        try {
-            String insertParam = req.getParameter("insert");
-            WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
-            Class<? extends AgentIdPojo> pojoCls = (Class<? extends AgentIdPojo>) Class.forName(insert.getPojoClass());
-            String pojoParam = req.getParameter("pojo");
-            AgentIdPojo pojo = gson.fromJson(pojoParam, pojoCls);
-            int categoryId = insert.getCategoryId();
-            Category category = getCategoryFromId(categoryId);
-            storage.putPojo(category, insert.isReplace(), pojo);
-            resp.setStatus(HttpServletResponse.SC_OK);
-        } catch (ClassNotFoundException ex) {
-            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
+
+        String insertParam = req.getParameter("insert");
+        WebInsert insert = gson.fromJson(insertParam, WebInsert.class);
+        int categoryId = insert.getCategoryId();
+        Category<?> category = getCategoryFromId(categoryId);
+        Class<? extends Pojo> pojoCls = category.getDataClass();
+        String pojoParam = req.getParameter("pojo");
+        Pojo pojo = gson.fromJson(pojoParam, pojoCls);
+        Put targetPut = insert.isReplace() ? storage.createReplace(category) : storage.createAdd(category);
+        targetPut.setPojo(pojo);
+        targetPut.apply();
+        resp.setStatus(HttpServletResponse.SC_OK);
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
@@ -282,12 +279,11 @@
         try {
             String updateParam = req.getParameter("update");
             WebUpdate update = gson.fromJson(updateParam, WebUpdate.class);
-            Update targetUpdate = storage.createUpdate();
-            targetUpdate = targetUpdate.from(getCategoryFromId(update.getCategoryId()));
+            Update targetUpdate = storage.createUpdate(getCategoryFromId(update.getCategoryId()));
             List<Qualifier<?>> qualifiers = update.getQualifiers();
             for (Qualifier qualifier : qualifiers) {
                 assert (qualifier.getCriteria() == Criteria.EQUALS);
-                targetUpdate = targetUpdate.where(qualifier.getKey(), qualifier.getValue());
+                targetUpdate.where(qualifier.getKey(), qualifier.getValue());
             }
             List<WebUpdate.UpdateValue> updates = update.getUpdates();
             if (updates != null) {
@@ -306,7 +302,7 @@
                     targetUpdate.set(key, value);
                 }
             }
-            storage.updatePojo(targetUpdate);
+            targetUpdate.apply();
             resp.setStatus(HttpServletResponse.SC_OK);
         } catch (ClassNotFoundException ex) {
             ex.printStackTrace();
@@ -315,57 +311,36 @@
     }
 
     @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void findPojo(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-        try {
-            String queryParam = req.getParameter("query");
-            WebQuery query = gson.fromJson(queryParam, WebQuery.class);
-            Class resultClass = Class.forName(query.getResultClassName());
-            Query targetQuery = constructTargetQuery(query);
-            Object result = storage.findPojo(targetQuery, resultClass);
-            writeResponse(resp, result);
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
+    private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        String queryParam = req.getParameter("query");
+        WebQuery query = gson.fromJson(queryParam, WebQuery.class);
+        Query targetQuery = constructTargetQuery(query);
+        ArrayList resultList = new ArrayList();
+        Cursor result = targetQuery.execute();
+        while (result.hasNext()) {
+            resultList.add(result.next());
         }
+        writeResponse(resp, resultList.toArray());
     }
 
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-        try {
-            String queryParam = req.getParameter("query");
-            WebQuery query = gson.fromJson(queryParam, WebQuery.class);
-            Class resultClass = Class.forName(query.getResultClassName());
-            Query targetQuery = constructTargetQuery(query);
-            ArrayList resultList = new ArrayList();
-            Cursor result = storage.findAllPojos(targetQuery, resultClass);
-            while (result.hasNext()) {
-                resultList.add(result.next());
-            }
-            writeResponse(resp, resultList.toArray());
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "result class not found");
-        }
-    }
+    private Query<?> constructTargetQuery(WebQuery<? extends Pojo> query) {
+        int categoryId = query.getCategoryId();
+        Category<?> category = getCategoryFromId(categoryId);
 
-    private Query constructTargetQuery(WebQuery query) {
-        Query targetQuery = storage.createQuery();
-        int categoryId = query.getCategoryId();
-        Category category = getCategoryFromId(categoryId);
-        targetQuery = targetQuery.from(category);
+        Query<?> targetQuery = storage.createQuery(category);
         List<Qualifier<?>> qualifiers = query.getQualifiers();
         for (Qualifier q : qualifiers) {
-            targetQuery = targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
+            targetQuery.where(q.getKey(), q.getCriteria(), q.getValue());
         }
         for (Sort s : query.getSorts()) {
-            targetQuery = targetQuery.sort(s.getKey(), s.getDirection());
+            targetQuery.sort(s.getKey(), s.getDirection());
         }
-        targetQuery = targetQuery.limit(query.getLimit());
+        targetQuery.limit(query.getLimit());
         return targetQuery;
     }
 
-    private Category getCategoryFromId(int categoryId) {
-        Category category = categories.get(categoryId);
+    private Category<?> getCategoryFromId(int categoryId) {
+        Category<?> category = categories.get(categoryId);
         return category;
     }
 
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/QueryTestHelper.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/QueryTestHelper.java	Mon Jan 14 14:04:50 2013 -0500
@@ -44,20 +44,10 @@
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Update;
 
 public class QueryTestHelper {
 
     @SuppressWarnings("unchecked")
-    public static Update createMockUpdate() {
-        Update mockUpdate = mock(Update.class);
-        when(mockUpdate.from(any(Category.class))).thenReturn(mockUpdate);
-        when(mockUpdate.where(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockUpdate.set(any(Key.class), any())).thenReturn(mockUpdate);
-        return mockUpdate;
-    }
-
-    @SuppressWarnings("unchecked")
     public static Remove createMockRemove() {
         Remove mockRemove = mock(Remove.class);
         when(mockRemove.from(any(Category.class))).thenReturn(mockRemove);
--- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Wed Jan 09 14:59:30 2013 -0500
+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java	Mon Jan 14 14:04:50 2013 -0500
@@ -37,13 +37,12 @@
 package com.redhat.thermostat.web.server;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
@@ -73,8 +72,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import sun.misc.BASE64Encoder;
-
 import com.google.gson.Gson;
 import com.redhat.thermostat.storage.core.Categories;
 import com.redhat.thermostat.storage.core.Category;
@@ -86,12 +83,12 @@
 import com.redhat.thermostat.storage.core.Query.Criteria;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.BasePojo;
 import com.redhat.thermostat.test.FreePortFinder;
 import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.test.MockQuery;
 import com.redhat.thermostat.web.common.StorageWrapper;
 import com.redhat.thermostat.web.common.WebInsert;
 import com.redhat.thermostat.web.common.WebQuery;
@@ -136,13 +133,13 @@
 
     private static Key<String> key1;
     private static Key<Integer> key2;
-    private static Category category;
+    private static Category<TestClass> category;
 
     @BeforeClass
     public static void setupCategory() {
         key1 = new Key<>("key1", true);
         key2 = new Key<>("key2", false);
-        category = new Category("test", key1, key2);
+        category = new Category<>("test", TestClass.class, key1, key2);
     }
 
     @AfterClass
@@ -201,42 +198,6 @@
     }
 
     @Test
-    public void testFind() throws IOException {
-        // Configure mock storage.
-        TestClass expected = new TestClass();
-        expected.setKey1("fluff");
-        expected.setKey2(42);
-        when(mockStorage.findPojo(any(Query.class), same(TestClass.class))).thenReturn(expected);
-
-        Query mockQuery = new MockQuery();
-        when(mockStorage.createQuery()).thenReturn(mockQuery);
-
-        String endpoint = getEndpoint();
-        URL url = new URL(endpoint + "/find-pojo");
-        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-        conn.setDoInput(true);
-        conn.setDoOutput(true);
-        Map<Category,Integer> categoryIdMap = new HashMap<>();
-        categoryIdMap.put(category, categoryId);
-        WebQuery query = (WebQuery) new WebQuery(categoryIdMap).from(category).where(key1, Criteria.EQUALS, "fluff");
-        query.setResultClassName(TestClass.class.getName());
-        Gson gson = new Gson();
-        OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
-        out.write("query=");
-        out.write(URLEncoder.encode(gson.toJson(query), "UTF-8"));
-        out.write("\n");
-        out.flush();
-
-        Reader in = new InputStreamReader(conn.getInputStream());
-        TestClass result = gson.fromJson(in, TestClass.class);
-
-        assertEquals("fluff", result.getKey1());
-        assertEquals(42, result.getKey2());
-        verify(mockStorage).createQuery();
-        verify(mockStorage).findPojo(any(Query.class), same(TestClass.class));
-    }
-
-    @Test
     public void testFindAllPojos() throws IOException {
         TestClass expected1 = new TestClass();
         expected1.setKey1("fluff1");
@@ -249,9 +210,9 @@
         when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
         when(cursor.next()).thenReturn(expected1).thenReturn(expected2);
 
-        when(mockStorage.findAllPojos(any(Query.class), same(TestClass.class))).thenReturn(cursor);
-        MockQuery mockQuery = new MockQuery();
-        when(mockStorage.createQuery()).thenReturn(mockQuery);
+        Query mockQuery = mock(Query.class);
+        when(mockStorage.createQuery(any(Category.class))).thenReturn(mockQuery);
+        when(mockQuery.execute()).thenReturn(cursor);
 
         String endpoint = getEndpoint();
         URL url = new URL(endpoint + "/find-all");
@@ -262,9 +223,10 @@
         conn.setDoOutput(true);
         Map<Category,Integer> categoryIdMap = new HashMap<>();
         categoryIdMap.put(category, categoryId);
-        WebQuery query = (WebQuery) new WebQuery(categoryIdMap).from(category).where(key1, Criteria.EQUALS, "fluff")
-                                                               .sort(key1, SortDirection.DESCENDING).limit(42);
-        query.setResultClassName(TestClass.class.getName());
+        WebQuery query = new WebQuery(categoryId);
+        query.where(key1, Criteria.EQUALS, "fluff");
+        query.sort(key1, SortDirection.DESCENDING);
+        query.limit(42);
         Gson gson = new Gson();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         String body = "query=" + URLEncoder.encode(gson.toJson(query), "UTF-8");
@@ -279,13 +241,19 @@
         assertEquals("fluff2", results[1].getKey1());
         assertEquals(43, results[1].getKey2());
 
-        assertTrue(mockQuery.hasSort(key1, SortDirection.DESCENDING));
-        assertEquals(42, mockQuery.getLimit());
+        verify(mockQuery).where(key1, Criteria.EQUALS, "fluff");
+        verify(mockQuery).sort(key1, SortDirection.DESCENDING);
+        verify(mockQuery).limit(42);
+        verify(mockQuery).execute();
+        verifyNoMoreInteractions(mockQuery);
     }
 
     @Test
     public void testPutPojo() throws IOException {
 
+        Replace replace = mock(Replace.class);
+        when(mockStorage.createReplace(any(Category.class))).thenReturn(replace);
+
         TestClass expected1 = new TestClass();
         expected1.setKey1("fluff1");
         expected1.setKey2(42);
@@ -299,7 +267,7 @@
 
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        WebInsert insert = new WebInsert(categoryId, true, TestClass.class.getName());
+        WebInsert insert = new WebInsert(categoryId, true);
         Gson gson = new Gson();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("insert=");
@@ -310,13 +278,14 @@
         out.write("\n");
         out.flush();
         assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).putPojo(category, true, expected1);
+        verify(mockStorage).createReplace(category);
+        verify(replace).setPojo(expected1);
+        verify(replace).apply();
     }
 
     private void sendAuthorization(HttpURLConnection conn, String username, String passwd) {
-        BASE64Encoder enc = new BASE64Encoder();
         String userpassword = username + ":" + passwd;
-        String encodedAuthorization = enc.encode( userpassword.getBytes() );
+        String encodedAuthorization = Base64.encodeBase64String(userpassword.getBytes());
         conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
     }
 
@@ -335,7 +304,7 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        Map<Category,Integer> categoryIds = new HashMap<>();
+        Map<Category<?>,Integer> categoryIds = new HashMap<>();
         categoryIds.put(category, categoryId);
         WebRemove remove = new WebRemove(categoryIds).from(category).where(key1, "test");
         Gson gson = new Gson();
@@ -356,10 +325,7 @@
     public void testUpdatePojo() throws IOException {
 
         Update mockUpdate = mock(Update.class);
-        when(mockUpdate.from(any(Category.class))).thenReturn(mockUpdate);
-        when(mockUpdate.where(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockUpdate.set(any(Key.class), any())).thenReturn(mockUpdate);
-        when(mockStorage.createUpdate()).thenReturn(mockUpdate);
+        when(mockStorage.createUpdate(any(Category.class))).thenReturn(mockUpdate);
 
         String endpoint = getEndpoint();
 
@@ -367,9 +333,13 @@
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setDoOutput(true);
         conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
-        Map<Category,Integer> categoryIds = new HashMap<>();
-        categoryIds.put(category, categoryId);
-        WebUpdate update = new WebUpdate(categoryIds).from(category).where(key1, "test").set(key1, "fluff").set(key2, 42);
+
+        WebUpdate update = new WebUpdate();
+        update.setCategoryId(categoryId);
+        update.where(key1, "test");
+        update.set(key1, "fluff");
+        update.set(key2, 42);
+
         Gson gson = new Gson();
         OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream());
         out.write("update=");
@@ -380,12 +350,12 @@
         out.flush();
 
         assertEquals(200, conn.getResponseCode());
-        verify(mockStorage).createUpdate();
-        verify(mockUpdate).from(category);
+        verify(mockStorage).createUpdate(category);
         verify(mockUpdate).where(key1, "test");
         verify(mockUpdate).set(key1, "fluff");
         verify(mockUpdate).set(key2, 42);
-        verify(mockStorage).updatePojo(mockUpdate);
+        verify(mockUpdate).apply();
+        verifyNoMoreInteractions(mockUpdate);
     }