changeset 103:dd82a9bcf94c

Implement client side for showing number of loaded classes.
author Roman Kennke <rkennke@redhat.com>
date Tue, 06 Mar 2012 22:44:19 +0100
parents 4acc8348d4fe
children 8ebd71ff78cc
files client/pom.xml client/src/main/java/com/redhat/thermostat/client/Connection.java client/src/main/java/com/redhat/thermostat/client/Main.java client/src/main/java/com/redhat/thermostat/client/MongoConnection.java client/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java client/src/main/java/com/redhat/thermostat/client/VmClassStatController.java client/src/main/java/com/redhat/thermostat/client/VmPanelFacade.java client/src/main/java/com/redhat/thermostat/client/VmPanelFacadeImpl.java client/src/main/java/com/redhat/thermostat/client/appctx/ApplicationContext.java client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java client/src/main/java/com/redhat/thermostat/client/ui/ConnectionSelectionDialog.java client/src/main/java/com/redhat/thermostat/client/ui/VmClassStatPanel.java client/src/main/java/com/redhat/thermostat/client/ui/VmClassStatView.java client/src/main/java/com/redhat/thermostat/client/ui/VmPanel.java client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties client/src/test/java/com/redhat/thermostat/client/VmClassStatControllerTest.java client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextTest.java client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextUtil.java client/src/test/java/com/redhat/thermostat/client/ui/VmClassStatPanelTest.java common/pom.xml common/src/main/java/com/redhat/thermostat/common/dao/Connection.java common/src/main/java/com/redhat/thermostat/common/dao/ConnectionProvider.java common/src/main/java/com/redhat/thermostat/common/dao/DAOFactory.java common/src/main/java/com/redhat/thermostat/common/dao/MongoConnection.java common/src/main/java/com/redhat/thermostat/common/dao/MongoConnectionProvider.java common/src/main/java/com/redhat/thermostat/common/dao/MongoDAOFactory.java common/src/main/java/com/redhat/thermostat/common/dao/MongoVmClassStatDAO.java common/src/main/java/com/redhat/thermostat/common/dao/VmClassStatDAO.java common/src/test/java/com/redhat/thermostat/common/dao/MongoDAOFactoryTest.java common/src/test/java/com/redhat/thermostat/common/dao/MongoVmClassStatDAOTest.java common/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java
diffstat 31 files changed, 1579 insertions(+), 369 deletions(-) [+]
line wrap: on
line diff
--- a/client/pom.xml	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/pom.xml	Tue Mar 06 22:44:19 2012 +0100
@@ -59,6 +59,12 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common</artifactId>
       <version>${project.version}</version>
--- a/client/src/main/java/com/redhat/thermostat/client/Connection.java	Tue Mar 06 22:43:32 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public abstract class Connection {
-
-    public enum ConnectionType {
-        LOCAL(false),
-        REMOTE(true),
-        CLUSTER(true),
-        ;
-
-        boolean isDisplayable = false;
-        boolean needsUrl = false;
-
-        private ConnectionType(boolean needsUrl) {
-            this.needsUrl = needsUrl;
-        }
-
-        private ConnectionType(boolean isDisplayable, boolean needsUrl) {
-            this.isDisplayable = isDisplayable;
-        }
-
-        public boolean isDisplayable() {
-            return isDisplayable;
-        }
-
-        public boolean needsUrl() {
-            return needsUrl;
-        }
-    }
-
-    public enum ConnectionStatus {
-        CONNECTED,
-        FAILED_TO_CONNECT,
-        DISCONNECTED,
-    }
-
-    public interface ConnectionListener {
-        public void changed(ConnectionStatus newStatus);
-    }
-
-    protected boolean connected = false;
-
-    private ConnectionType type;
-    private String url;
-
-    private List<ConnectionListener> listeners = new CopyOnWriteArrayList<ConnectionListener>();
-
-    public void setType(ConnectionType type) {
-        this.type = type;
-    }
-
-    public ConnectionType getType() {
-        return type;
-    }
-
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    @Override
-    public String toString() {
-        if (url == null) {
-            return type.toString();
-        }
-        return type.toString() + " to " + url;
-    }
-
-    public abstract void connect();
-
-    public abstract void disconnect();
-
-    public boolean isConnected() {
-        return connected;
-    }
-
-    public void addListener(ConnectionListener listener) {
-        this.listeners.add(listener);
-    }
-
-    public void removeListener(ConnectionListener listener) {
-        this.listeners.remove(listener);
-    }
-
-    protected void fireChanged(ConnectionStatus status) {
-        for (ConnectionListener listener: listeners) {
-            listener.changed(status);
-        }
-    }
-}
--- a/client/src/main/java/com/redhat/thermostat/client/Main.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/Main.java	Tue Mar 06 22:44:19 2012 +0100
@@ -46,12 +46,19 @@
 import javax.swing.JPopupMenu;
 import javax.swing.SwingUtilities;
 
-import com.redhat.thermostat.client.Connection.ConnectionListener;
-import com.redhat.thermostat.client.Connection.ConnectionStatus;
+import com.redhat.thermostat.client.appctx.ApplicationContext;
 import com.redhat.thermostat.client.locale.LocaleResources;
 import com.redhat.thermostat.client.ui.ConnectionSelectionDialog;
 import com.redhat.thermostat.client.ui.MainWindow;
 import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.dao.Connection;
+import com.redhat.thermostat.common.dao.Connection.ConnectionListener;
+import com.redhat.thermostat.common.dao.Connection.ConnectionStatus;
+import com.redhat.thermostat.common.dao.ConnectionProvider;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.MongoConnection;
+import com.redhat.thermostat.common.dao.MongoConnectionProvider;
+import com.redhat.thermostat.common.dao.MongoDAOFactory;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
 public class Main {
@@ -59,7 +66,6 @@
     private static final Logger logger = LoggingUtils.getLogger(Main.class);
 
     private ClientArgs arguments;
-    private Connection connection;
     private UiFacadeFactory uiFacadeFactory;
 
     private Main(String[] args) {
@@ -70,12 +76,15 @@
             System.exit(-1);
         }
 
-        connection = new MongoConnection(arguments.getProperties());
+        ConnectionProvider connProv = new MongoConnectionProvider(arguments.getProperties());
+        DAOFactory daoFactory = new MongoDAOFactory(connProv);
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
     }
 
     private void showGui() {
         JPopupMenu.setDefaultLightWeightPopupEnabled(false);
 
+        Connection connection = ApplicationContext.getInstance().getDAOFactory().getConnection();
         ConnectionSelectionDialog dialog = new ConnectionSelectionDialog((JFrame) null, connection);
         dialog.pack();
         dialog.setModal(true);
--- a/client/src/main/java/com/redhat/thermostat/client/MongoConnection.java	Tue Mar 06 22:43:32 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.UnknownHostException;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import com.mongodb.DB;
-import com.mongodb.Mongo;
-import com.mongodb.MongoException;
-import com.mongodb.MongoURI;
-import com.redhat.thermostat.common.Constants;
-import com.redhat.thermostat.common.NotImplementedException;
-import com.redhat.thermostat.common.storage.StorageConstants;
-import com.redhat.thermostat.common.utils.LoggedExternalProcess;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-
-public class MongoConnection extends Connection {
-    private static final Logger logger = LoggingUtils.getLogger(MongoConnection.class);
-
-    private Mongo m = null;
-    private DB db = null;
-    private boolean hasLocalAgent = false;
-    private Process localAgentProcess = null;
-    private Properties props;
-
-    public MongoConnection(Properties props) {
-        this.props = props;
-    }
-
-    @Override
-    public void connect() {
-        try {
-            m = new Mongo(getMongoURI());
-            db = m.getDB(StorageConstants.THERMOSTAT_DB_NAME);
-            /* the mongo java driver does not ensure this connection is actually working */
-            testConnection(db);
-        } catch (MongoException e) {
-            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
-            return;
-        } catch (UnknownHostException e) {
-            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
-            return;
-        } catch (LocalAgentException e) {
-            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
-            return;
-        } catch (NotImplementedException e) {
-            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
-            return;
-        }
-        fireChanged(ConnectionStatus.CONNECTED);
-        connected = true;
-    }
-
-    private static void testConnection(DB db) {
-        db.getCollection("agent-config").getCount();
-    }
-
-    private MongoURI getMongoURI() {
-        MongoURI uri = null;
-        switch (getType()) {
-            case LOCAL:
-                startLocalAgent();
-                uri = new MongoURI("mongodb://127.0.0.1:"
-                        + props.getProperty(Constants.AGENT_PROPERTY_MONGOD_PORT));
-                break;
-            case REMOTE:
-                throw new NotImplementedException("No mongo URI implemented for REMOTE.");
-            case CLUSTER:
-                throw new NotImplementedException("No mongo URI implemented for CLUSTER.");
-        }
-        return uri;
-    }
-
-    private void startLocalAgent() throws LocalAgentException {
-        int status = 0;
-        try {
-            String agentScript = props.getProperty(Constants.CLIENT_PROPERTY_AGENT_LAUNCH_SCRIPT);
-            localAgentProcess = new LoggedExternalProcess(new String[] { agentScript, "--local" }).runAndReturnProcess();
-            // Allow some time for things to get started.
-            try {
-                // TODO provide some UI feedback here instead of just seeming dead.
-                Thread.sleep(2000);
-            } catch (InterruptedException e) {
-                // ignore
-            }
-        } catch (IOException e) {
-            throw new LocalAgentException();
-        }
-        if (status != 0) {
-            throw new LocalAgentException();
-        }
-        hasLocalAgent = true;
-    }
-
-    public DB getDB() {
-        return db;
-    }
-
-    @Override
-    public void disconnect() {
-        if (m != null) {
-            m.close();
-        }
-        if (hasLocalAgent) {
-            stopLocalAgent();
-        }
-        connected = false;
-    }
-
-    private void stopLocalAgent() {
-        // TODO this is currently using Agent's 'run until some data avail on stdin' hack.
-        // That hack will go away, at which point we will need another way to shut down.
-        OutputStream agentIn = localAgentProcess.getOutputStream();
-        byte[] anything = { 0x04 };
-        try {
-            agentIn.write(anything);
-            agentIn.flush();
-            localAgentProcess.waitFor();
-        } catch (IOException e) {
-            logger.warning("Error shutting down local agent.");
-        } catch (InterruptedException e) {
-            logger.warning("Interrupted waiting for local agent to shut down.");
-        }
-    }
-
-    private class LocalAgentException extends RuntimeException {
-    }
-}
--- a/client/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/UiFacadeFactoryImpl.java	Tue Mar 06 22:44:19 2012 +0100
@@ -37,10 +37,12 @@
 package com.redhat.thermostat.client;
 
 import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.MongoConnection;
 import com.redhat.thermostat.common.dao.VmRef;
 
 public class UiFacadeFactoryImpl implements UiFacadeFactory {
 
+    // TODO: Eventually, this should disappear and be completely provided by the DAOFactory.
     private MongoConnection connection;
 
     public UiFacadeFactoryImpl(MongoConnection connection) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/VmClassStatController.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import javax.swing.SwingWorker;
+
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import com.redhat.thermostat.client.appctx.ApplicationContext;
+import com.redhat.thermostat.client.ui.VmClassStatPanel;
+import com.redhat.thermostat.client.ui.VmClassStatView;
+import com.redhat.thermostat.common.VmClassStat;
+import com.redhat.thermostat.common.dao.VmClassStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class VmClassStatController implements AsyncUiFacade {
+
+    private static final Logger logger = LoggingUtils.getLogger(VmClassStatController.class);
+
+    private class UpdateChartDataTimerTask extends TimerTask {
+
+        @Override
+        public void run() {
+            UpdateChartDataWorker worker = new UpdateChartDataWorker();
+            worker.execute();
+        }
+        
+    }
+
+    private class UpdateChartDataWorker extends SwingWorker<List<DiscreteTimeData<Long>>, Void> {
+
+        @Override
+        protected List<DiscreteTimeData<Long>> doInBackground() throws Exception {
+            List<VmClassStat> latestClassStats = dao.getLatestClassStats();
+            List<DiscreteTimeData<Long>> timeData = new ArrayList<>();
+            for (VmClassStat stat : latestClassStats) {
+                timeData.add(new DiscreteTimeData<Long>(stat.getTimestamp(), stat.getLoadedClasses()));
+            }
+            
+            return timeData;
+        }
+        
+        @Override
+        protected void done() {
+            try {
+                appendCollectorDataToChart(get(), classSeries);
+            } catch (ExecutionException ee) {
+                logger.throwing("VmClassStatController.UpdateChartDataWorker", "done", ee);
+            } catch (InterruptedException ie) {
+                // Preserve interrupted flag to let the EDT handle this.
+                Thread.currentThread().interrupt();
+            }
+        }
+
+        private void appendCollectorDataToChart(List<DiscreteTimeData<Long>> collectorData, TimeSeries collectorSeries) {
+            if (collectorData.size() > 0) {
+
+                /*
+                 * We have lots of new data to add. we do it in 2 steps:
+                 * 1. Add everything with notify off.
+                 * 2. Notify the chart that there has been a change. It
+                 * does all the expensive computations and redraws itself.
+                 */
+
+                for (DiscreteTimeData<Long> data : collectorData) {
+                    collectorSeries.add(
+                            new FixedMillisecond(data.getTimeInMillis()), data.getData(),
+                            /* notify = */false);
+                }
+
+                collectorSeries.fireSeriesChanged();
+            }
+
+        }
+    }
+
+    private VmClassStatView classesView;
+
+    // TODO: Use application wide ScheduledExecutorService thread pool.
+    private Timer timer;
+
+    private VmClassStatDAO dao;
+
+    private TimeSeries classSeries;
+    private TimeSeriesCollection classSeriesCollection;
+
+    public VmClassStatController(VmRef ref) {
+        dao = ApplicationContext.getInstance().getDAOFactory().getVmClassStatsDAO(ref);
+        classesView = createView();
+        classSeries = new TimeSeries("loadedClasses");
+        classSeriesCollection = new TimeSeriesCollection(classSeries);
+        classesView.setDataSet(classSeriesCollection);
+    }
+
+    protected VmClassStatView createView() {
+        return new VmClassStatPanel();
+    }
+
+    @Override
+    public void start() {
+        if (timer == null) {
+            timer = new Timer();
+        }
+        TimerTask updateTimerTask = new UpdateChartDataTimerTask();
+        timer.scheduleAtFixedRate(updateTimerTask, 0, TimeUnit.SECONDS.toMillis(5));
+    }
+
+    @Override
+    public void stop() {
+        timer.cancel();
+        timer = null;
+    }
+
+    public Component getComponent() {
+        return classesView.getUIComponent();
+    }
+
+}
--- a/client/src/main/java/com/redhat/thermostat/client/VmPanelFacade.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/VmPanelFacade.java	Tue Mar 06 22:44:19 2012 +0100
@@ -82,4 +82,6 @@
 
     public ChangeableText getVmArguments();
 
+    public VmClassStatController getClassesController();
+
 }
--- a/client/src/main/java/com/redhat/thermostat/client/VmPanelFacadeImpl.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/VmPanelFacadeImpl.java	Tue Mar 06 22:44:19 2012 +0100
@@ -99,6 +99,8 @@
 
     private VmMemoryStat cached;
 
+    private VmClassStatController classesController;
+
     public VmPanelFacadeImpl(VmRef vmRef, DB db) {
         this.ref = vmRef;
         vmInfoCollection = db.getCollection("vm-info");
@@ -106,6 +108,8 @@
         vmMemoryStatsCollection = db.getCollection("vm-memory-stats");
 
         vmRunningTimeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.FULL);
+
+        classesController = new VmClassStatController(vmRef);
     }
 
     @Override
@@ -167,11 +171,15 @@
 
         }, 0, TimeUnit.SECONDS.toMillis(5));
 
+        classesController.start();
+
     }
 
     @Override
     public void stop() {
         timer.cancel();
+
+        classesController.stop();
     }
 
     @Override
@@ -458,4 +466,9 @@
         return currentMemoryDataset;
     }
 
+    @Override
+    public VmClassStatController getClassesController() {
+        return classesController;
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/appctx/ApplicationContext.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.appctx;
+
+import com.redhat.thermostat.common.dao.DAOFactory;
+
+public class ApplicationContext {
+
+    private static ApplicationContext instance = new ApplicationContext();
+
+    private DAOFactory daoFactory;
+
+    public static ApplicationContext getInstance() {
+        return instance;
+    }
+
+    static void reset() {
+        instance = new ApplicationContext();
+    }
+
+    private ApplicationContext() {
+        // Nothing to do here, just prevent instantiation of this class outside
+        // the factory method.
+    }
+
+    public void setDAOFactory(DAOFactory daoFactory) {
+        this.daoFactory = daoFactory;
+    }
+
+    public DAOFactory getDAOFactory() {
+        return daoFactory;
+    }
+
+}
--- a/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/locale/LocaleResources.java	Tue Mar 06 22:44:19 2012 +0100
@@ -162,7 +162,12 @@
 
     VM_GC_COLLECTOR_OVER_GENERATION,
     VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL,
-    VM_GC_COLLECTOR_CHART_GC_TIME_LABEL;
+    VM_GC_COLLECTOR_CHART_GC_TIME_LABEL,
+
+    VM_INFO_TAB_CLASSES,
+    VM_LOADED_CLASSES,
+    VM_CLASSES_CHART_REAL_TIME_LABEL,
+    VM_CLASSES_CHART_LOADED_CLASSES_LABEL;
     
     static final String RESOURCE_BUNDLE =
             "com.redhat.thermostat.client.locale.strings";
--- a/client/src/main/java/com/redhat/thermostat/client/ui/ConnectionSelectionDialog.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/ConnectionSelectionDialog.java	Tue Mar 06 22:44:19 2012 +0100
@@ -57,9 +57,9 @@
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 
-import com.redhat.thermostat.client.Connection;
-import com.redhat.thermostat.client.Connection.ConnectionType;
 import com.redhat.thermostat.client.locale.LocaleResources;
+import com.redhat.thermostat.common.dao.Connection;
+import com.redhat.thermostat.common.dao.Connection.ConnectionType;
 
 public class ConnectionSelectionDialog extends JDialog {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmClassStatPanel.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static com.redhat.thermostat.client.locale.Translate.localize;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+
+import javax.swing.JPanel;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import com.redhat.thermostat.client.locale.LocaleResources;
+
+public class VmClassStatPanel extends JPanel implements VmClassStatView {
+
+    private Component chartPanel;
+
+    public VmClassStatPanel() {
+        setBorder(Components.smallBorder());
+        setLayout(new BorderLayout());
+
+        GridBagConstraints c = new GridBagConstraints();
+        c.gridx = 0;
+        c.fill = GridBagConstraints.BOTH;
+
+        add(Components.header(localize(LocaleResources.VM_LOADED_CLASSES)), BorderLayout.NORTH);
+    }
+
+    public void setDataSet(TimeSeriesCollection dataset) {
+        if (chartPanel != null) {
+            remove(chartPanel);
+        }
+
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+                null,
+                localize(LocaleResources.VM_CLASSES_CHART_REAL_TIME_LABEL),
+                localize(LocaleResources.VM_CLASSES_CHART_LOADED_CLASSES_LABEL),
+                dataset,
+                false, false, false);
+
+        chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart));
+
+        add(chartPanel, BorderLayout.CENTER);
+
+    }
+
+    @Override
+    public Component getUIComponent() {
+        return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmClassStatView.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import java.awt.Component;
+
+import org.jfree.data.time.TimeSeriesCollection;
+
+public interface VmClassStatView {
+
+    void setDataSet(TimeSeriesCollection dataset);
+    Component getUIComponent();
+}
--- a/client/src/main/java/com/redhat/thermostat/client/ui/VmPanel.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/java/com/redhat/thermostat/client/ui/VmPanel.java	Tue Mar 06 22:44:19 2012 +0100
@@ -82,6 +82,7 @@
         tabPane.insertTab(localize(LocaleResources.VM_INFO_TAB_MEMORY), null, createMemoryPanel(), null, 1);
         tabPane.insertTab(localize(LocaleResources.VM_INFO_TAB_GC), null, createGcPanel(),
                           localize(LocaleResources.GARBAGE_COLLECTION), 2);
+        tabPane.insertTab(localize(LocaleResources.VM_INFO_TAB_CLASSES), null, facade.getClassesController().getComponent(), null, 3);
 
         // TODO additional tabs provided by plugins
         // tabPane.insertTab(title, icon, component, tip, 3)
--- a/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Tue Mar 06 22:43:32 2012 +0100
+++ b/client/src/main/resources/com/redhat/thermostat/client/locale/strings.properties	Tue Mar 06 22:44:19 2012 +0100
@@ -126,3 +126,8 @@
 VM_GC_COLLECTOR_OVER_GENERATION = Collector {0} running on {1}
 VM_GC_COLLECTOR_CHART_REAL_TIME_LABEL = Time
 VM_GC_COLLECTOR_CHART_GC_TIME_LABEL = Total Time Spent on GC (s)
+
+VM_LOADED_CLASSES = Loaded Classes
+VM_CLASSES_CHART_REAL_TIME_LABEL = Time
+VM_CLASSES_CHART_LOADED_CLASSES_LABEL = Number of loaded classes
+VM_INFO_TAB_CLASSES = Classes
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/VmClassStatControllerTest.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jfree.data.general.DatasetChangeEvent;
+import org.jfree.data.general.DatasetChangeListener;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.redhat.thermostat.client.appctx.ApplicationContext;
+import com.redhat.thermostat.client.ui.VmClassStatView;
+import com.redhat.thermostat.common.VmClassStat;
+import com.redhat.thermostat.common.dao.DAOFactory;
+import com.redhat.thermostat.common.dao.MongoDAOFactory;
+import com.redhat.thermostat.common.dao.MongoVmClassStatDAO;
+import com.redhat.thermostat.common.dao.VmRef;
+
+public class VmClassStatControllerTest {
+
+    @Test
+    public void testChartUpdate() {
+
+        VmClassStat stat1 = new VmClassStat(123, 12345, 1234);
+        List<VmClassStat> stats = new ArrayList<VmClassStat>();
+        stats.add(stat1);
+
+        MongoVmClassStatDAO vmClassStatDAO = mock(MongoVmClassStatDAO.class);
+        when(vmClassStatDAO.getLatestClassStats()).thenReturn(stats).thenReturn(new ArrayList<VmClassStat>());
+
+        DAOFactory daoFactory = mock(MongoDAOFactory.class);
+        when(daoFactory.getVmClassStatsDAO(any(VmRef.class))).thenReturn(vmClassStatDAO);
+
+        ApplicationContext.getInstance().setDAOFactory(daoFactory);
+        VmRef ref = mock(VmRef.class);
+
+        final DatasetChangeListener l = mock(DatasetChangeListener.class);
+        final VmClassStatView view = mock(VmClassStatView.class);
+        doAnswer(new Answer<Void>() {
+
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                TimeSeriesCollection timeSeriesColl = (TimeSeriesCollection) args[0];
+                timeSeriesColl.addChangeListener(l);
+                return null;
+            }
+            
+        }).when(view).setDataSet(any(TimeSeriesCollection.class));
+
+        // TODO: Consider to pass the ClassesView or a factory for it to the controller instead.
+        VmClassStatController controller = new VmClassStatController(ref) {
+            protected VmClassStatView createView() {
+                return view;
+            }
+        };
+
+        controller.start();
+
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+            // Get out of here ASAP.
+            return;
+        }
+
+        verify(l, atLeast(1)).datasetChanged(any(DatasetChangeEvent.class));
+        // We don't verify atMost() since we might increase the update rate in the future.
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextTest.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.appctx;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.client.appctx.ApplicationContext;
+import com.redhat.thermostat.common.dao.DAOFactory;
+
+public class ApplicationContextTest {
+
+    @Before
+    public void setUp() {
+        ApplicationContext.reset();
+    }
+
+    @After
+    public void tearDown() {
+        ApplicationContext.reset();
+    }
+
+    @Test
+    public void verifyGetInstanceNotNull() {
+        ApplicationContext ctx = ApplicationContext.getInstance();
+        assertNotNull(ctx);
+    }
+
+    @Test
+    public void  testDAOFactorySetGet() {
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        ApplicationContext ctx = ApplicationContext.getInstance();
+        ctx.setDAOFactory(daoFactory);
+
+        DAOFactory actual1 = ctx.getDAOFactory();
+        assertSame(daoFactory, actual1);
+    }
+
+    @Test
+    public void  verifyDAOFactoryIsNullWhenNotInitialized() {
+        ApplicationContext ctx = ApplicationContext.getInstance();
+
+        DAOFactory actual = ctx.getDAOFactory();
+        assertNull(actual);
+    }
+
+    @Test
+    public void  verifyDAOFactoryStaysSame() {
+        DAOFactory daoFactory = mock(DAOFactory.class);
+        ApplicationContext ctx = ApplicationContext.getInstance();
+        ctx.setDAOFactory(daoFactory);
+
+        ApplicationContext ctx2 = ApplicationContext.getInstance();
+        DAOFactory actual2 = ctx2.getDAOFactory();
+        assertSame(daoFactory, actual2);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextUtil.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.appctx;
+
+public class ApplicationContextUtil {
+
+    /**
+     * This is here to allow tests to reset the ApplicationContext, while
+     * preventing real code the same (ApplicationContext.reset() is package private).
+     *
+     * It is vital that tests call this from their setUp() and tearDown() methods,
+     * to avoid leaking mocks and stuff from test to test.
+     */
+    public static void resetApplicationContext() {
+        ApplicationContext.reset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/test/java/com/redhat/thermostat/client/ui/VmClassStatPanelTest.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.ui;
+
+import static org.junit.Assert.assertEquals;
+
+import org.jfree.data.time.TimeSeriesCollection;
+import org.junit.Test;
+
+public class VmClassStatPanelTest {
+
+    @Test
+    public void testSetDataSetTwice() {
+        VmClassStatPanel panel = new VmClassStatPanel();
+        TimeSeriesCollection dataSet = new TimeSeriesCollection();
+        panel.setDataSet(dataSet);
+        int numComponents = panel.getComponentCount();
+        panel.setDataSet(dataSet);
+        assertEquals(numComponents, panel.getComponentCount());
+    }
+
+}
--- a/common/pom.xml	Tue Mar 06 22:43:32 2012 +0100
+++ b/common/pom.xml	Tue Mar 06 22:44:19 2012 +0100
@@ -75,6 +75,18 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <version>1.4.11</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <version>1.4.11</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>mongo-java-driver</artifactId>
       <version>2.7.3</version>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/Connection.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public abstract class Connection {
+
+    public enum ConnectionType {
+        LOCAL(false),
+        REMOTE(true),
+        CLUSTER(true),
+        ;
+
+        boolean isDisplayable = false;
+        boolean needsUrl = false;
+
+        private ConnectionType(boolean needsUrl) {
+            this.needsUrl = needsUrl;
+        }
+
+        private ConnectionType(boolean isDisplayable, boolean needsUrl) {
+            this.isDisplayable = isDisplayable;
+        }
+
+        public boolean isDisplayable() {
+            return isDisplayable;
+        }
+
+        public boolean needsUrl() {
+            return needsUrl;
+        }
+    }
+
+    public enum ConnectionStatus {
+        CONNECTED,
+        FAILED_TO_CONNECT,
+        DISCONNECTED,
+    }
+
+    public interface ConnectionListener {
+        public void changed(ConnectionStatus newStatus);
+    }
+
+    protected boolean connected = false;
+
+    private ConnectionType type;
+    private String url;
+
+    private List<ConnectionListener> listeners = new CopyOnWriteArrayList<ConnectionListener>();
+
+    public void setType(ConnectionType type) {
+        this.type = type;
+    }
+
+    public ConnectionType getType() {
+        return type;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public String toString() {
+        if (url == null) {
+            return type.toString();
+        }
+        return type.toString() + " to " + url;
+    }
+
+    public abstract void connect();
+
+    public abstract void disconnect();
+
+    public boolean isConnected() {
+        return connected;
+    }
+
+    public void addListener(ConnectionListener listener) {
+        this.listeners.add(listener);
+    }
+
+    public void removeListener(ConnectionListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    protected void fireChanged(ConnectionStatus status) {
+        for (ConnectionListener listener: listeners) {
+            listener.changed(status);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/ConnectionProvider.java	Tue Mar 06 22:44:19 2012 +0100
@@ -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.common.dao;
+
+public interface ConnectionProvider {
+
+    Connection createConnection();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/DAOFactory.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+public interface DAOFactory {
+
+    public abstract Connection getConnection();
+
+    public abstract VmClassStatDAO getVmClassStatsDAO(VmRef ref);
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/MongoConnection.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.UnknownHostException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import com.mongodb.DB;
+import com.mongodb.Mongo;
+import com.mongodb.MongoException;
+import com.mongodb.MongoURI;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.storage.StorageConstants;
+import com.redhat.thermostat.common.utils.LoggedExternalProcess;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class MongoConnection extends Connection {
+    private static final Logger logger = LoggingUtils.getLogger(MongoConnection.class);
+
+    private Mongo m = null;
+    private DB db = null;
+    private boolean hasLocalAgent = false;
+    private Process localAgentProcess = null;
+    private Properties props;
+
+    public MongoConnection(Properties props) {
+        this.props = props;
+    }
+
+    @Override
+    public void connect() {
+        try {
+            m = new Mongo(getMongoURI());
+            db = m.getDB(StorageConstants.THERMOSTAT_DB_NAME);
+            /* the mongo java driver does not ensure this connection is actually working */
+            testConnection(db);
+        } catch (MongoException e) {
+            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
+            return;
+        } catch (UnknownHostException e) {
+            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
+            return;
+        } catch (LocalAgentException e) {
+            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
+            return;
+        } catch (NotImplementedException e) {
+            fireChanged(ConnectionStatus.FAILED_TO_CONNECT);
+            return;
+        }
+        fireChanged(ConnectionStatus.CONNECTED);
+        connected = true;
+    }
+
+    private static void testConnection(DB db) {
+        db.getCollection("agent-config").getCount();
+    }
+
+    private MongoURI getMongoURI() {
+        MongoURI uri = null;
+        switch (getType()) {
+            case LOCAL:
+                startLocalAgent();
+                uri = new MongoURI("mongodb://127.0.0.1:"
+                        + props.getProperty(Constants.AGENT_PROPERTY_MONGOD_PORT));
+                break;
+            case REMOTE:
+                throw new NotImplementedException("No mongo URI implemented for REMOTE.");
+            case CLUSTER:
+                throw new NotImplementedException("No mongo URI implemented for CLUSTER.");
+        }
+        return uri;
+    }
+
+    private void startLocalAgent() throws LocalAgentException {
+        int status = 0;
+        try {
+            String agentScript = props.getProperty(Constants.CLIENT_PROPERTY_AGENT_LAUNCH_SCRIPT);
+            localAgentProcess = new LoggedExternalProcess(new String[] { agentScript, "--local" }).runAndReturnProcess();
+            // Allow some time for things to get started.
+            try {
+                // TODO provide some UI feedback here instead of just seeming dead.
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        } catch (IOException e) {
+            throw new LocalAgentException();
+        }
+        if (status != 0) {
+            throw new LocalAgentException();
+        }
+        hasLocalAgent = true;
+    }
+
+    public DB getDB() {
+        return db;
+    }
+
+    @Override
+    public void disconnect() {
+        if (m != null) {
+            m.close();
+        }
+        if (hasLocalAgent) {
+            stopLocalAgent();
+        }
+        connected = false;
+    }
+
+    private void stopLocalAgent() {
+        // TODO this is currently using Agent's 'run until some data avail on stdin' hack.
+        // That hack will go away, at which point we will need another way to shut down.
+        OutputStream agentIn = localAgentProcess.getOutputStream();
+        byte[] anything = { 0x04 };
+        try {
+            agentIn.write(anything);
+            agentIn.flush();
+            localAgentProcess.waitFor();
+        } catch (IOException e) {
+            logger.warning("Error shutting down local agent.");
+        } catch (InterruptedException e) {
+            logger.warning("Interrupted waiting for local agent to shut down.");
+        }
+    }
+
+    private class LocalAgentException extends RuntimeException {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/MongoConnectionProvider.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import java.util.Properties;
+
+public class MongoConnectionProvider implements ConnectionProvider {
+
+    private Properties connectionProperties;
+
+    public MongoConnectionProvider(Properties connProps) {
+        connectionProperties = connProps;
+    }
+
+    @Override
+    public Connection createConnection() {
+        return new MongoConnection(connectionProperties);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/MongoDAOFactory.java	Tue Mar 06 22:44:19 2012 +0100
@@ -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.common.dao;
+
+import java.util.Properties;
+
+
+public class MongoDAOFactory implements DAOFactory {
+
+    private MongoConnection connection;
+
+    public MongoDAOFactory(ConnectionProvider connProv) {
+        connection = (MongoConnection) connProv.createConnection();
+    }
+
+    @Override
+    public Connection getConnection() {
+        return connection;
+    }
+
+    @Override
+    public VmClassStatDAO getVmClassStatsDAO(VmRef ref) {
+        return new MongoVmClassStatDAO(connection.getDB(), ref);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/MongoVmClassStatDAO.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.redhat.thermostat.common.VmClassStat;
+
+public class MongoVmClassStatDAO implements VmClassStatDAO {
+
+    private VmRef ref;
+    private DBCollection vmClassStatsCollection;
+
+    private long lastUpdate = Long.MIN_VALUE;
+
+    public MongoVmClassStatDAO(DB db, VmRef vmRef) {
+        ref = vmRef;
+        vmClassStatsCollection = db.getCollection("vm-class-stats");
+    }
+
+    @Override
+    public List<VmClassStat> getLatestClassStats() {
+        ArrayList<VmClassStat> result = new ArrayList<>();
+        BasicDBObject queryObject = new BasicDBObject();
+        queryObject.put("agent-id", ref.getAgent().getAgentId());
+        queryObject.put("vm-id", Integer.valueOf(ref.getId()));
+        if (lastUpdate != Long.MIN_VALUE) {
+            // TODO once we have an index and the 'column' is of type long, use
+            // a query which can utilize an index. this one doesn't
+            queryObject.put("$where", "this.timestamp > " + lastUpdate);
+        }
+        DBCursor cursor = vmClassStatsCollection.find(queryObject);
+        long timestamp;
+        Long loadedClasses;
+        while (cursor.hasNext()) {
+            DBObject current = cursor.next();
+            timestamp = (Long) current.get("timestamp");
+            loadedClasses = (Long) current.get("loadedClasses");
+            int vmId = (Integer) current.get("vm-id");
+            result.add(new VmClassStat(vmId, timestamp, loadedClasses));
+            lastUpdate = Math.max(timestamp, lastUpdate);
+        }
+
+        return result;
+    }
+}
--- a/common/src/main/java/com/redhat/thermostat/common/dao/VmClassStatDAO.java	Tue Mar 06 22:43:32 2012 +0100
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/VmClassStatDAO.java	Tue Mar 06 22:44:19 2012 +0100
@@ -36,14 +36,21 @@
 
 package com.redhat.thermostat.common.dao;
 
+import java.util.List;
+
+import com.redhat.thermostat.common.VmClassStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Key;
 
-public class VmClassStatDAO {
+public interface VmClassStatDAO {
 
     static final Key<Integer> vmIdKey = new Key<>("vm-id", false);
     static final Key<Long> loadedClassesKey = new Key<>("loadedClasses", false);
 
-    public static final Category vmClassStatsCategory = new Category("vm-class-stats",
-            vmIdKey, Key.TIMESTAMP, loadedClassesKey);
-}
+
+    public static final Category vmClassStatsCategory = new Category(
+            "vm-class-stats", vmIdKey, Key.TIMESTAMP, loadedClassesKey);
+
+    public abstract List<VmClassStat> getLatestClassStats();
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/common/dao/MongoDAOFactoryTest.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import com.mongodb.DB;
+
+public class MongoDAOFactoryTest {
+
+    @Test
+    public void testGetConnection() {
+        Connection conn = mock(MongoConnection.class);
+
+        ConnectionProvider connProvider = mock(ConnectionProvider.class);
+        when(connProvider.createConnection()).thenReturn(conn);
+
+        DAOFactory daoFactory = new MongoDAOFactory(connProvider);
+        assertSame(conn, daoFactory.getConnection());
+
+    }
+
+    @Test
+    public void testGetVmClassStatsDAO() {
+
+        DB db = mock(DB.class);
+
+        MongoConnection conn = mock(MongoConnection.class);
+        when(conn.getDB()).thenReturn(db);
+
+        ConnectionProvider connProv = mock(ConnectionProvider.class);
+        when(connProv.createConnection()).thenReturn(conn);
+
+        DAOFactory instance = new MongoDAOFactory(connProv);
+
+        VmRef ref = mock(VmRef.class);
+        VmClassStatDAO vmClassStatsDAO = instance.getVmClassStatsDAO(ref);
+
+        assertNotNull(vmClassStatsDAO);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/common/dao/MongoVmClassStatDAOTest.java	Tue Mar 06 22:44:19 2012 +0100
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.dao;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+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.when;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.redhat.thermostat.common.VmClassStat;
+import com.redhat.thermostat.common.storage.Key;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({DBCollection.class, DB.class })
+public class MongoVmClassStatDAOTest {
+
+    @Test
+    public void testCategory() {
+        assertEquals("vm-class-stats", VmClassStatDAO.vmClassStatsCategory.getName());
+        Collection<Key<?>> keys = VmClassStatDAO.vmClassStatsCategory.getKeys();
+        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
+        assertTrue(keys.contains(new Key<Long>("timestamp", false)));
+        assertTrue(keys.contains(new Key<Long>("loadedClasses", false)));
+        assertEquals(3, keys.size());
+
+    }
+
+    @Test
+    public void testGetLatestClassStatsBasic() {
+
+        DBObject dbValue = PowerMockito.mock(DBObject.class);
+        when(dbValue.get("timestamp")).thenReturn(1234L);
+        when(dbValue.get("vm-id")).thenReturn(321);
+        when(dbValue.get("loadedClasses")).thenReturn(12345L);
+
+        DBCursor cursor = mock(DBCursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(dbValue);
+
+        DBCollection vmClassStatsCollection = PowerMockito.mock(DBCollection.class);
+        when(vmClassStatsCollection.find(any(DBObject.class))).thenReturn(cursor);
+
+        DB db = mock(DB.class);
+        when(db.getCollection("vm-class-stats")).thenReturn(vmClassStatsCollection);
+
+        HostRef hostRef = mock(HostRef.class);
+        when(hostRef.getAgentId()).thenReturn("system");
+
+        VmRef vmRef = mock(VmRef.class);
+        when(vmRef.getAgent()).thenReturn(hostRef);
+        when(vmRef.getId()).thenReturn("321");
+
+
+        VmClassStatDAO dao = new MongoVmClassStatDAO(db, vmRef);
+        List<VmClassStat> vmClassStats = dao.getLatestClassStats();
+
+        ArgumentCaptor<DBObject> arg = ArgumentCaptor.forClass(DBObject.class);
+        verify(vmClassStatsCollection).find(arg.capture());
+        assertNull(arg.getValue().get("$where"));
+
+        assertEquals(1, vmClassStats.size());
+        VmClassStat stat = vmClassStats.get(0);
+        assertEquals(1234L, stat.getTimestamp());
+        assertEquals(12345L, stat.getLoadedClasses());
+        assertEquals(321, stat.getVmId());
+    }
+
+    @Test
+    public void testGetLatestClassStatsTwice() {
+
+        DBObject dbValue = PowerMockito.mock(DBObject.class);
+        when(dbValue.get("timestamp")).thenReturn(1234L);
+        when(dbValue.get("vm-id")).thenReturn(321);
+        when(dbValue.get("loadedClasses")).thenReturn(12345L);
+
+        DBCursor cursor = mock(DBCursor.class);
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(dbValue);
+
+        DBCollection vmClassStatsCollection = PowerMockito.mock(DBCollection.class);
+        when(vmClassStatsCollection.find(any(DBObject.class))).thenReturn(cursor);
+
+        DB db = mock(DB.class);
+        when(db.getCollection("vm-class-stats")).thenReturn(vmClassStatsCollection);
+
+        HostRef hostRef = mock(HostRef.class);
+        when(hostRef.getAgentId()).thenReturn("system");
+
+        VmRef vmRef = mock(VmRef.class);
+        when(vmRef.getAgent()).thenReturn(hostRef);
+        when(vmRef.getId()).thenReturn("321");
+
+
+        VmClassStatDAO dao = new MongoVmClassStatDAO(db, vmRef);
+        dao.getLatestClassStats();
+
+        dao.getLatestClassStats();
+        ArgumentCaptor<DBObject> arg = ArgumentCaptor.forClass(DBObject.class);
+        verify(vmClassStatsCollection, times(2)).find(arg.capture());
+        assertEquals("this.timestamp > 1234", arg.getValue().get("$where"));
+    }
+}
--- a/common/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Tue Mar 06 22:43:32 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.common.dao;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Collection;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.storage.Key;
-
-public class VmClassStatDAOTest {
-
-    @Test
-    public void testCategory() {
-        assertEquals("vm-class-stats", VmClassStatDAO.vmClassStatsCategory.getName());
-        Collection<Key<?>> keys = VmClassStatDAO.vmClassStatsCategory.getKeys();
-        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
-        assertTrue(keys.contains(new Key<Long>("timestamp", false)));
-        assertTrue(keys.contains(new Key<Long>("loadedClasses", false)));
-        assertEquals(3, keys.size());
-
-    }
-}