changeset 576:4fbe27356f96

Merge
author Roman Kennke <rkennke@redhat.com>
date Fri, 31 Aug 2012 11:26:14 +0200
parents 0375b44c73d4 (current diff) 06f2dcf9ad3b (diff)
children d03acb3cf6c6
files common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java distribution/config/bundles.properties distribution/pom.xml thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadCollector.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadCollectorFactory.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadInfo.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadSummary.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/VMThreadCapabilities.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadCollectorFactoryImpl.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXBeanCollector.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXInfo.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXSummary.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/VMThreadMXCapabilities.java thread/collector/src/main/java/com/redhat/thermostat/thread/collector/osgi/Activator.java thread/collector/src/main/java/com/redhat/thermostat/utils/management/MXBeanConnection.java thread/collector/src/main/java/com/redhat/thermostat/utils/management/MXBeanConnector.java thread/collector/src/test/java/com/redhat/thermostat/thread/collector/ThreadCollectorFactoryTest.java thread/collector/src/test/java/com/redhat/thermostat/thread/collector/ThreadCollectorTest.java
diffstat 75 files changed, 2841 insertions(+), 1696 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/AgentApplication.java	Fri Aug 31 11:26:14 2012 +0200
@@ -64,6 +64,7 @@
 import com.redhat.thermostat.common.storage.Connection.ConnectionListener;
 import com.redhat.thermostat.common.storage.Connection.ConnectionStatus;
 import com.redhat.thermostat.common.storage.MongoStorageProvider;
+import com.redhat.thermostat.common.storage.Storage;
 import com.redhat.thermostat.common.storage.StorageProvider;
 import com.redhat.thermostat.common.tools.BasicCommand;
 import com.redhat.thermostat.common.utils.LoggingUtils;
@@ -106,7 +107,7 @@
         final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
 
         StorageProvider connProv = new MongoStorageProvider(configuration);
-        DAOFactory daoFactory = new MongoDAOFactory(connProv);
+        final DAOFactory daoFactory = new MongoDAOFactory(connProv);
         ApplicationContext.getInstance().setDAOFactory(daoFactory);
         TimerFactory timerFactory = new ThreadPoolTimerFactory(1);
         ApplicationContext.getInstance().setTimerFactory(timerFactory);
@@ -123,7 +124,9 @@
                     logger.fine("Connecting to storage.");
                     break;
                 case CONNECTED:
-                    logger.fine("Connected to storage.");
+                    logger.fine("Connected to storage, registering storage as service");
+                    Storage storage = daoFactory.getStorage();
+                    OSGIUtils.getInstance().registerService(Storage.class, storage);
                     break;
                 case FAILED_TO_CONNECT:
                     logger.warning("Could not connect to storage.");
--- a/agent/command/src/main/java/com/redhat/thermostat/agent/command/ReceiverRegistry.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/agent/command/src/main/java/com/redhat/thermostat/agent/command/ReceiverRegistry.java	Fri Aug 31 11:26:14 2012 +0200
@@ -60,3 +60,4 @@
         proxy.unregisterAll();
     }
 }
+
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Chunk.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Chunk.java	Fri Aug 31 11:26:14 2012 +0200
@@ -100,4 +100,9 @@
         return Objects.equals(this.category, other.category) && Objects.equals(this.values, other.values);
     }
 
+    @Override
+    public String toString() {
+        return "Chunk: " + category.getName() + values.toString();
+    }
+
 }
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/Key.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/Key.java	Fri Aug 31 11:26:14 2012 +0200
@@ -44,8 +44,8 @@
 
     // Keys used by most Categories.
     public static final Key<Long> TIMESTAMP = new Key<>("timestamp", false);
-    public static final Key<String> AGENT_ID = new Key<>("agent-id", false);
-    public static final Key<Integer> VM_ID = new Key<>("vm-id", false);
+    public static final Key<String> AGENT_ID = new Key<>("agent-id", true);
+    public static final Key<Integer> VM_ID = new Key<>("vm-id", true);
     public static final Key<String> WHERE = new Key<>("$where", false);
     public static final Key<String> ID = new Key<>("_id", false);
 
--- a/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/main/java/com/redhat/thermostat/common/storage/MongoStorage.java	Fri Aug 31 11:26:14 2012 +0200
@@ -141,7 +141,7 @@
                 BasicDBObject nested = nestedParts.get(entryParts[0]);
                 if (nested == null) {
                     if (isKey) {
-                        throwMissingKey(key.getName());
+                        throwMissingKey(key.getName(), chunk);
                     }
                     nested = new BasicDBObject();
                     nestedParts.put(entryParts[0], nested);
@@ -156,11 +156,12 @@
                     replaceKeyNested.append(entryParts[1], replaceKeyNested);
                 }
             } else {
+                /* we dont modify agent id, and it's already used as key in updateKey */
                 if (!key.equals(Key.AGENT_ID)) {
                     String mongoKey = key.getName();
                     Object value = chunk.get(key);
                     if ((value == null) && isKey) {
-                        throwMissingKey(key.getName());
+                        throwMissingKey(key.getName(), chunk);
                     }
                     toInsert.append(mongoKey, value);
                     if (replace && isKey) {
@@ -197,7 +198,7 @@
                 BasicDBObject nested = nestedParts.get(entryParts[0]);
                 if (nested == null) {
                     if (isKey) {
-                        throwMissingKey(key.getName());
+                        throwMissingKey(key.getName(), chunk);
                     }
                 } else {
                     if (isKey) {
@@ -213,16 +214,19 @@
                 }
             } else {
                 String mongoKey = key.getName();
-                Object value = chunk.get(key);
-                if (value == null) {
-                    if (isKey) {
-                        throwMissingKey(key.getName());
-                    }
-                } else {
-                    if (isKey) {
-                        updateKey.append(mongoKey, value);
+                /* we dont modify agent id, and it's already used as key in updateKey */
+                if (!key.equals(Key.AGENT_ID)) {
+                    Object value = chunk.get(key);
+                    if (value == null) {
+                        if (isKey) {
+                            throwMissingKey(key.getName(), chunk);
+                        }
                     } else {
-                        toUpdate.append(SET_MODIFIER, new BasicDBObject(mongoKey, value));
+                        if (isKey) {
+                            updateKey.append(mongoKey, value);
+                        } else {
+                            toUpdate.append(SET_MODIFIER, new BasicDBObject(mongoKey, value));
+                        }
                     }
                 }
             }
@@ -236,8 +240,8 @@
         coll.update(updateKey, toUpdate);
     }
 
-    private void throwMissingKey(String keyName) {
-        throw new IllegalArgumentException("Attempt to insert chunk with incomplete partial key.  Missing: " + keyName);
+    private void throwMissingKey(String keyName, Chunk chunk) {
+        throw new IllegalArgumentException("Attempt to insert chunk with incomplete partial key.  Missing: '" + keyName + "' in " + chunk);
     }
 
     private DBCollection getCachedCollection(String collName) {
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/CpuStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -67,7 +67,7 @@
     public void testCategory() {
         assertEquals("cpu-stats", CpuStatDAO.cpuStatCategory.getName());
         Collection<Key<?>> keys = CpuStatDAO.cpuStatCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<Double>("processor-usage", false)));
 
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -189,8 +189,8 @@
         Collection<Key<?>> keys = category.getKeys();
         assertEquals(6, keys.size());
         assertTrue(keys.contains(new Key<>("_id", false)));
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
-        assertTrue(keys.contains(new Key<>("vm-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
+        assertTrue(keys.contains(new Key<>("vm-id", true)));
         assertTrue(keys.contains(new Key<>("timestamp", false)));
         assertTrue(keys.contains(new Key<>("heap-dump-id", false)));
         assertTrue(keys.contains(new Key<>("histogram-id", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HostInfoDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -72,7 +72,7 @@
     public void testCategory() {
         assertEquals("host-info", HostInfoDAO.hostInfoCategory.getName());
         Collection<Key<?>> keys = HostInfoDAO.hostInfoCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<String>("hostname", true)));
         assertTrue(keys.contains(new Key<String>("os_name", false)));
         assertTrue(keys.contains(new Key<String>("os_kernel", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/MemoryStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -74,7 +74,7 @@
     public void testCategory() {
         assertEquals("memory-stats", MemoryStatDAO.memoryStatCategory.getName());
         Collection<Key<?>> keys = MemoryStatDAO.memoryStatCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<Long>("total", false)));
         assertTrue(keys.contains(new Key<Long>("free", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/NetworkInterfaceInfoDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -69,7 +69,7 @@
 
         assertEquals("network-info", NetworkInterfaceInfoDAO.networkInfoCategory.getName());
         keys = NetworkInterfaceInfoDAO.networkInfoCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<String>("iface", true)));
         assertTrue(keys.contains(new Key<String>("ipv4addr", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatConverterTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatConverterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -55,7 +55,7 @@
 
         assertEquals("vm-class-stats", chunk.getCategory().getName());
         assertEquals((Long) 1234L, chunk.get(Key.TIMESTAMP));
-        assertEquals((Integer) 123, chunk.get(new Key<Integer>("vm-id", false)));
+        assertEquals((Integer) 123, chunk.get(new Key<Integer>("vm-id", true)));
         assertEquals((Long) 12345L, chunk.get(new Key<Long>("loadedClasses", false)));
     }
 
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmClassStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -67,8 +67,8 @@
     public void testCategory() {
         assertEquals("vm-class-stats", VmClassStatDAO.vmClassStatsCategory.getName());
         Collection<Key<?>> keys = VmClassStatDAO.vmClassStatsCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
-        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
+        assertTrue(keys.contains(new Key<Integer>("vm-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<Long>("loadedClasses", false)));
         assertEquals(4, keys.size());
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatConverterTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatConverterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -58,7 +58,7 @@
         assertNotNull(chunk);
         assertEquals("vm-cpu-stats", chunk.getCategory().getName());
         assertEquals((Long)TIMESTAMP, chunk.get(Key.TIMESTAMP));
-        assertEquals((Integer) VM_ID, chunk.get(new Key<Long>("vm-id", false)));
+        assertEquals((Integer) VM_ID, chunk.get(new Key<Long>("vm-id", true)));
         assertEquals(PROCESSOR_USAGE, chunk.get(new Key<Double>("processor-usage", false)), 0.001);
 
     }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmCpuStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -80,9 +80,9 @@
     public void testCategory() {
         assertEquals("vm-cpu-stats", VmCpuStatDAO.vmCpuStatCategory.getName());
         Collection<Key<?>> keys = VmCpuStatDAO.vmCpuStatCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
-        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
+        assertTrue(keys.contains(new Key<Integer>("vm-id", true)));
         assertTrue(keys.contains(new Key<Integer>("processor-usage", false)));
         assertEquals(4, keys.size());
     }
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatConverterTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatConverterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -60,7 +60,7 @@
         assertNotNull(chunk);
         assertEquals("vm-gc-stats", chunk.getCategory().getName());
         assertEquals(TIMESTAMP, chunk.get(new Key<Long>("timestamp", false)));
-        assertEquals(VM_ID, chunk.get(new Key<Integer>("vm-id", false)));
+        assertEquals(VM_ID, chunk.get(new Key<Integer>("vm-id", true)));
         assertEquals(COLLECTOR, chunk.get(new Key<String>("collector", false)));
         assertEquals(RUN_COUNT, chunk.get(new Key<Long>("runtime-count", false)));
         assertEquals(WALL_TIME, chunk.get(new Key<Long>("wall-time", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmGcStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -70,8 +70,8 @@
     public void testCategory() {
         assertEquals("vm-gc-stats", VmGcStatDAO.vmGcStatCategory.getName());
         Collection<Key<?>> keys = VmGcStatDAO.vmGcStatCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
-        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
+        assertTrue(keys.contains(new Key<Integer>("vm-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<String>("collector", false)));
         assertTrue(keys.contains(new Key<Long>("runtime-count", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmInfoDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -109,7 +109,7 @@
     public void testCategory() {
         assertEquals("vm-info", VmInfoDAO.vmInfoCategory.getName());
         Collection<Key<?>> keys = VmInfoDAO.vmInfoCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
         assertTrue(keys.contains(new Key<Integer>("vm-id", true)));
         assertTrue(keys.contains(new Key<Integer>("vm-pid", false)));
         assertTrue(keys.contains(new Key<String>("runtime-version", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatConverterTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatConverterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -88,7 +88,7 @@
 
         assertNotNull(chunk);
         assertEquals((Long) 1l, chunk.get(new Key<Long>("timestamp", false)));
-        assertEquals((Integer) 2, chunk.get(new Key<Integer>("vm-id", false)));
+        assertEquals((Integer) 2, chunk.get(new Key<Integer>("vm-id", true)));
         assertEquals("new", chunk.get(new Key<String>("eden.gen", false)));
         assertEquals("new", chunk.get(new Key<String>("eden.collector", false)));
         assertEquals((Long) 0l, chunk.get(new Key<Long>("eden.used", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -116,8 +116,8 @@
 
         assertEquals("vm-memory-stats", VmMemoryStatDAO.vmMemoryStatsCategory.getName());
         keys = VmMemoryStatDAO.vmMemoryStatsCategory.getKeys();
-        assertTrue(keys.contains(new Key<>("agent-id", false)));
-        assertTrue(keys.contains(new Key<Integer>("vm-id", false)));
+        assertTrue(keys.contains(new Key<>("agent-id", true)));
+        assertTrue(keys.contains(new Key<Integer>("vm-id", true)));
         assertTrue(keys.contains(new Key<Long>("timestamp", false)));
         assertTrue(keys.contains(new Key<String>("eden.gen", false)));
         assertTrue(keys.contains(new Key<String>("eden.collector", false)));
@@ -234,7 +234,7 @@
 
         assertEquals(VmMemoryStatDAO.vmMemoryStatsCategory, chunk.getCategory());
         assertEquals((Long) 1l, chunk.get(new Key<Long>("timestamp", false)));
-        assertEquals((Integer) 2, chunk.get(new Key<Integer>("vm-id", false)));
+        assertEquals((Integer) 2, chunk.get(new Key<Integer>("vm-id", true)));
         assertEquals("new", chunk.get(new Key<String>("eden.gen", false)));
         assertEquals("new", chunk.get(new Key<String>("eden.collector", false)));
         assertEquals((Long) 0l, chunk.get(new Key<Long>("eden.used", false)));
--- a/common/core/src/test/java/com/redhat/thermostat/common/storage/KeyTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/common/core/src/test/java/com/redhat/thermostat/common/storage/KeyTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -130,4 +130,14 @@
         String string1 = key1.toString();
         assertEquals(string1, "Key: key1");
     }
+
+    @Test
+    public void verifyAgentIdIsPartialkey() {
+        assertTrue(Key.AGENT_ID.isPartialCategoryKey());
+    }
+
+    @Test
+    public void verifyVmIdIsPartialkey() {
+        assertTrue(Key.VM_ID.isPartialCategoryKey());
+    }
 }
--- a/distribution/config/bundles.properties	Fri Aug 31 11:22:52 2012 +0200
+++ b/distribution/config/bundles.properties	Fri Aug 31 11:26:14 2012 +0200
@@ -20,7 +20,10 @@
       thermostat-thread-client-swing-@project.version@.jar, \
       thermostat-thread-client-controllers-@project.version@.jar, \
       thermostat-thread-client-common-@project.version@.jar, \
-      thermostat-osgi-process-handler-@project.version@.jar
+      thermostat-osgi-process-handler-@project.version@.jar, \
+      thermostat-client-command-@project.version@.jar, \
+      thermostat-client-command-@project.version@.jar, \
+      thermostat-agent-command-@project.version@.jar
 
 service = thermostat-agent-core-@project.version@.jar, \
           thermostat-osgi-process-handler-@project.version@.jar, \
@@ -36,6 +39,9 @@
         thermostat-common-command-@project.version@.jar, \
         thermostat-agent-command-@project.version@.jar, \
         thermostat-agent-heapdumper-@project.version@.jar
+        thermostat-thread-collector-@project.version@.jar, \
+        thermostat-thread-harvester-@project.version@.jar, \
+        thermostat-client-command-@project.version@.jar
 
 storage = thermostat-agent-core-@project.version@.jar, \
           thermostat-osgi-process-handler-@project.version@.jar, \
--- a/distribution/pom.xml	Fri Aug 31 11:22:52 2012 +0200
+++ b/distribution/pom.xml	Fri Aug 31 11:26:14 2012 +0200
@@ -352,6 +352,11 @@
         <artifactId>thermostat-thread-collector</artifactId>
         <version>${project.version}</version>
     </dependency>      
-  
+    <dependency>
+        <groupId>com.redhat.thermostat</groupId>
+        <artifactId>thermostat-thread-harvester</artifactId>
+        <version>${project.version}</version>
+    </dependency>
+     
   </dependencies>
 </project>
--- a/thread/client-common/pom.xml	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-common/pom.xml	Fri Aug 31 11:26:14 2012 +0200
@@ -102,11 +102,17 @@
           <instructions>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Bundle-SymbolicName>com.redhat.thermostat.thread.client.common</Bundle-SymbolicName>
+            <Bundle-Activator>com.redhat.thermostat.thread.client.common.osgi.Activator</Bundle-Activator>
             <Export-Package>
               com.redhat.thermostat.thread.client.common,
               com.redhat.thermostat.thread.client.common.locale,
               com.redhat.thermostat.thread.client.common.chart,
+              com.redhat.thermostat.thread.client.common.collector,
             </Export-Package>
+            <Private-Package>
+              com.redhat.thermostat.thread.client.common.collector.impl,
+              com.redhat.thermostat.thread.client.common.osgi,
+            </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
           </instructions>
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadView.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadView.java	Fri Aug 31 11:26:14 2012 +0200
@@ -62,7 +62,7 @@
         notifier.removeActionListener(listener);
     }
     
-    public abstract void setRecording(boolean recording);
+    public abstract void setRecording(boolean recording, boolean notify);
     
     public abstract void setDaemonThreads(String daemonThreads);
     public abstract void setLiveThreads(String liveThreads);
@@ -70,4 +70,6 @@
     
     public abstract VMThreadCapabilitiesView createVMThreadCapabilitiesView();
     public abstract ThreadTableView createThreadTableView();
+    
+    public abstract void displayWarning(String warning);
 }
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/VMThreadCapabilitiesView.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/VMThreadCapabilitiesView.java	Fri Aug 31 11:26:14 2012 +0200
@@ -37,7 +37,7 @@
 package com.redhat.thermostat.thread.client.common;
 
 import com.redhat.thermostat.client.osgi.service.BasicView;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public abstract class VMThreadCapabilitiesView extends BasicView {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/collector/ThreadCollector.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.client.common.collector;
+
+import java.util.List;
+
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+
+public interface ThreadCollector {
+    
+    VMThreadCapabilities getVMThreadCapabilities();
+    
+    boolean startHarvester();
+    boolean stopHarvester();
+    boolean isHarvesterCollecting();
+    
+    ThreadSummary getLatestThreadSummary();
+    List<ThreadSummary> getThreadSummary(long since);
+    List<ThreadSummary> getThreadSummary();
+    
+    /**
+     * Return a list of {@link ThreadInfoData}, sorted in descending order their by
+     * {@link ThreadInfoData#getTimeStamp()}, whose elements are at most
+     * "{@code since}" old.
+     */
+    List<ThreadInfoData> getThreadInfo(long since);
+
+    /**
+     * Return a list of all the {@link ThreadInfoData} collected, sorted in
+     * descending order their by {@link ThreadInfoData#getTimeStamp()}.
+     */
+    List<ThreadInfoData> getThreadInfo();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/collector/ThreadCollectorFactory.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common.collector;
+
+import com.redhat.thermostat.common.dao.VmRef;
+
+public interface ThreadCollectorFactory {
+
+    ThreadCollector getCollector(VmRef reference);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/collector/impl/ThreadCollectorFactoryImpl.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.client.common.collector.impl;
+
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+
+public class ThreadCollectorFactoryImpl implements ThreadCollectorFactory {
+
+    private ThreadDao threadDao;
+    
+    public ThreadCollectorFactoryImpl(ThreadDao threadDao) {
+        this.threadDao = threadDao;
+    }
+    
+    @Override
+    public synchronized ThreadCollector getCollector(VmRef reference) {
+        return new ThreadMXBeanCollector(threadDao, reference);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/collector/impl/ThreadMXBeanCollector.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,248 @@
+/*
+ * 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.thread.client.common.collector.impl;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.utils.OSGIUtils;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.HarvesterCommand;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+
+public class ThreadMXBeanCollector implements ThreadCollector {
+
+    private ThreadDao threadDao;
+    private VmRef ref;
+
+    public ThreadMXBeanCollector(ThreadDao threadDao, VmRef ref) {
+        this.threadDao = threadDao;
+        this.ref = ref;
+    }
+
+    Request createRequest() {
+        HostRef targetHostRef = ref.getAgent();
+        
+        // todo
+        String address = threadDao.getStorage().getConfigListenAddress(targetHostRef);
+        String [] host = address.split(":");
+        
+        InetSocketAddress target = new InetSocketAddress(host[0], Integer.parseInt(host[1]));
+        Request harvester = new Request(RequestType.RESPONSE_EXPECTED, target);
+
+        harvester.setReceiver(HarvesterCommand.RECEIVER);
+        
+        return harvester;
+    }
+    
+    @Override
+    public boolean startHarvester() {
+        
+        Request harvester = createRequest();
+        harvester.setParameter(HarvesterCommand.class.getName(), HarvesterCommand.START.name());
+        harvester.setParameter(HarvesterCommand.VM_ID.name(), ref.getIdString());
+        harvester.setParameter(HarvesterCommand.AGENT_ID.name(), ref.getAgent().getAgentId());
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        
+        harvester.addListener(new RequestResponseListener() {
+            @Override
+            public void fireComplete(Request request, Response response) {
+                switch (response.getType()) {
+                case OK:
+                    result[0] = true;
+                    break;
+                default:
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        RequestQueue queue = getRequestQueue();        
+        queue.putRequest(harvester);
+
+        try {
+            latch.await();
+        } catch (InterruptedException ignore) {}
+        
+        return result[0];
+    }
+
+    @Override
+    public boolean stopHarvester() {
+        
+        Request harvester = createRequest();
+        harvester.setParameter(HarvesterCommand.class.getName(), HarvesterCommand.STOP.name());
+        harvester.setParameter(HarvesterCommand.VM_ID.name(), ref.getIdString());
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+
+        harvester.addListener(new RequestResponseListener() {
+            @Override
+            public void fireComplete(Request request, Response response) {
+                switch (response.getType()) {
+                case OK:
+                    result[0] = true;
+                    break;
+                default:
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        RequestQueue queue = getRequestQueue();
+        queue.putRequest(harvester);
+
+        try {
+            latch.await();
+        } catch (InterruptedException ignore) {}
+        return result[0];
+    }
+    
+    @Override
+    public boolean isHarvesterCollecting() {
+        Request harvester = createRequest();
+        harvester.setParameter(HarvesterCommand.class.getName(), HarvesterCommand.IS_COLLECTING.name());
+        harvester.setParameter(HarvesterCommand.VM_ID.name(), ref.getIdString());
+
+        final CountDownLatch latch = new CountDownLatch(1);        
+        final boolean[] result = new boolean[1];
+
+        harvester.addListener(new RequestResponseListener() {
+            @Override
+            public void fireComplete(Request request, Response response) {
+                switch (response.getType()) {
+                case OK:
+                    result[0] = true;
+                    break;
+                default:
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        RequestQueue queue = getRequestQueue();
+        queue.putRequest(harvester);
+
+        try {
+            latch.await();
+        } catch (InterruptedException ignore) {}
+        return result[0];
+    }
+    
+    @Override
+    public ThreadSummary getLatestThreadSummary() {
+        ThreadSummary summary = threadDao.loadLastestSummary(ref);
+        if (summary == null) {
+            // default to all 0
+            summary = new ThreadSummary();
+        }
+        return summary;
+    }
+    
+    @Override
+    public List<ThreadSummary> getThreadSummary(long since) {
+        List<ThreadSummary> summary = threadDao.loadSummary(ref, since);
+        return summary;
+    }
+    
+    @Override
+    public List<ThreadSummary> getThreadSummary() {
+        return getThreadSummary(0);
+    }
+    
+    @Override
+    public List<ThreadInfoData> getThreadInfo() {
+        return getThreadInfo(0);
+    }
+    
+    @Override
+    public List<ThreadInfoData> getThreadInfo(long since) {
+        return threadDao.loadThreadInfo(ref, since);
+    }
+
+    @Override
+    public VMThreadCapabilities getVMThreadCapabilities() {
+        
+        VMThreadCapabilities caps = threadDao.loadCapabilities(ref);
+        if (caps == null) {
+            Request harvester = createRequest();
+            harvester.setParameter(HarvesterCommand.class.getName(), HarvesterCommand.VM_CAPS.name());
+            harvester.setParameter(HarvesterCommand.VM_ID.name(), ref.getIdString());
+            harvester.setParameter(HarvesterCommand.AGENT_ID.name(), ref.getAgent().getAgentId());
+            
+            final CountDownLatch latch = new CountDownLatch(1);
+            harvester.addListener(new RequestResponseListener() {
+                @Override
+                public void fireComplete(Request request, Response response) {
+                    latch.countDown();
+                }
+            });
+
+            RequestQueue queue = getRequestQueue();
+            queue.putRequest(harvester);
+        
+            try {
+                latch.await();
+                caps = threadDao.loadCapabilities(ref);
+            } catch (InterruptedException ignore) {
+                caps = new VMThreadCapabilities();
+            }
+        }
+        return caps;
+    }
+    
+    RequestQueue getRequestQueue() {
+        return OSGIUtils.getInstance().getService(RequestQueue.class); 
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/osgi/Activator.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,77 @@
+/*
+ * 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.thread.client.common.osgi;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.client.common.collector.impl.ThreadCollectorFactoryImpl;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.dao.impl.ThreadDaoImpl;
+
+public class Activator implements BundleActivator {
+    
+    private ThreadCollectorFactoryImpl collectorFactory;
+    
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        ServiceTracker tracker = new ServiceTracker(context, ThreadDao.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                
+                ThreadDao threadDao = (ThreadDao) context.getService(reference);
+                
+                collectorFactory = new ThreadCollectorFactoryImpl(threadDao);
+
+                context.registerService(ThreadCollectorFactory.class.getName(), collectorFactory, null);
+
+                return super.addingService(reference);
+            }
+        };
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/test/java/com/redhat/thermostat/thread/client/common/collector/ThreadCollectorFactoryTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,63 @@
+/*
+ * 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.thread.client.common.collector;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.client.common.collector.impl.ThreadCollectorFactoryImpl;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+
+public class ThreadCollectorFactoryTest {
+
+    @Test
+    public void testThreadCollectorFactory() {
+        ThreadDao threadDao = mock(ThreadDao.class);
+        VmRef reference = mock(VmRef.class);
+                
+        ThreadCollectorFactory factory = new ThreadCollectorFactoryImpl(threadDao);
+        ThreadCollector collector = factory.getCollector(reference);
+        assertNotNull(collector);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/test/java/com/redhat/thermostat/thread/client/common/collector/impl/ThreadCollectorTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,256 @@
+/*
+ * 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.thread.client.common.collector.impl;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.collector.impl.ThreadMXBeanCollector;
+import com.redhat.thermostat.thread.collector.HarvesterCommand;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+
+public class ThreadCollectorTest {
+
+    @Test
+    public void testVMCapabilitiesNotInDAO() throws Exception {
+        
+        HostRef agent = mock(HostRef.class);
+        when(agent.getAgentId()).thenReturn("42");
+        
+        VmRef reference = mock(VmRef.class);
+        when(reference.getIdString()).thenReturn("00101010");
+        when(reference.getAgent()).thenReturn(agent);
+        ThreadDao threadDao = mock(ThreadDao.class);
+        
+        VMThreadCapabilities resCaps = mock(VMThreadCapabilities.class);
+        when(threadDao.loadCapabilities(reference)).thenReturn(null).thenReturn(resCaps);
+        
+        final Request request = mock(Request.class);
+        final RequestQueue requestQueue = mock(RequestQueue.class);
+        
+        final ArgumentCaptor<RequestResponseListener> captor = ArgumentCaptor.forClass(RequestResponseListener.class);
+        doNothing().when(request).addListener(captor.capture());
+        
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Request req = (Request) invocation.getArguments()[0];
+                assertSame(request, req);
+                
+                RequestResponseListener listener = captor.getValue();
+                listener.fireComplete(null, null);
+                
+                return null;
+            }
+
+        }).when(requestQueue).putRequest(request);
+                
+        /* ************* */
+        
+        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference) {
+            @Override
+            Request createRequest() {
+                return request;
+            }
+            @Override
+            RequestQueue getRequestQueue() {
+                return requestQueue;
+            }
+        };
+        
+        VMThreadCapabilities caps = collector.getVMThreadCapabilities();
+
+        verify(request).setParameter(HarvesterCommand.class.getName(), HarvesterCommand.VM_CAPS.name());
+        verify(request).setParameter(HarvesterCommand.VM_ID.name(), "00101010");
+        verify(request).setParameter(HarvesterCommand.AGENT_ID.name(), "42");
+        
+        verify(requestQueue).putRequest(request);
+        
+        verify(threadDao, times(2)).loadCapabilities(reference);
+        assertSame(resCaps, caps);
+    }
+    
+    @Test
+    public void testVMCapabilitiesInDAO() throws Exception {
+        
+        VmRef reference = mock(VmRef.class);
+        ThreadDao threadDao = mock(ThreadDao.class);
+        
+        VMThreadCapabilities resCaps = mock(VMThreadCapabilities.class);
+        when(threadDao.loadCapabilities(reference)).thenReturn(resCaps);
+        
+        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference) {
+            @Override
+            Request createRequest() {
+                fail();
+                return null;
+            }
+            @Override
+            RequestQueue getRequestQueue() {
+                fail();
+                return null;
+            }
+        };
+        
+        VMThreadCapabilities caps = collector.getVMThreadCapabilities();
+ 
+        verify(threadDao, times(1)).loadCapabilities(reference);
+        assertSame(resCaps, caps);
+    }
+    
+    @Test
+    public void testStart() {
+        
+        HostRef agent = mock(HostRef.class);
+        when(agent.getAgentId()).thenReturn("42");
+        
+        final Request request = mock(Request.class);
+        final RequestQueue requestQueue = mock(RequestQueue.class);
+        ThreadDao threadDao = mock(ThreadDao.class);
+        VmRef reference = mock(VmRef.class);
+        when(reference.getIdString()).thenReturn("00101010");
+        when(reference.getAgent()).thenReturn(agent);
+        
+        final Response response = mock(Response.class);
+        when(response.getType()).thenReturn(ResponseType.OK);
+        
+        final ArgumentCaptor<RequestResponseListener> captor = ArgumentCaptor.forClass(RequestResponseListener.class);
+        doNothing().when(request).addListener(captor.capture());
+        
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Request req = (Request) invocation.getArguments()[0];
+                assertSame(request, req);
+                
+                RequestResponseListener listener = captor.getValue();
+                listener.fireComplete(request, response);
+                
+                return null;
+            }
+
+        }).when(requestQueue).putRequest(request);
+        
+        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference) {
+            @Override
+            Request createRequest() {
+                return request;
+            }
+            @Override
+            RequestQueue getRequestQueue() {
+                return requestQueue;
+            }
+        };
+        
+        collector.startHarvester();
+        
+        verify(request).setParameter(HarvesterCommand.class.getName(), HarvesterCommand.START.name());
+        verify(request).setParameter(HarvesterCommand.VM_ID.name(), "00101010");
+        verify(request).setParameter(HarvesterCommand.AGENT_ID.name(), "42");
+        
+        verify(requestQueue).putRequest(request);
+    }
+    
+    
+    @Test
+    public void testStop() {
+
+        final Request request = mock(Request.class);
+        final RequestQueue requestQueue = mock(RequestQueue.class);
+        ThreadDao threadDao = mock(ThreadDao.class);
+        VmRef reference = mock(VmRef.class);
+        when(reference.getIdString()).thenReturn("00101010");
+        
+        final Response response = mock(Response.class);
+        when(response.getType()).thenReturn(ResponseType.OK);
+        
+        final ArgumentCaptor<RequestResponseListener> captor = ArgumentCaptor.forClass(RequestResponseListener.class);
+        doNothing().when(request).addListener(captor.capture());
+        
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Request req = (Request) invocation.getArguments()[0];
+                assertSame(request, req);
+                
+                RequestResponseListener listener = captor.getValue();
+                listener.fireComplete(request, response);
+                
+                return null;
+            }
+
+        }).when(requestQueue).putRequest(request);
+        
+        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference) {
+            @Override
+            Request createRequest() {
+                return request;
+            }
+            @Override
+            RequestQueue getRequestQueue() {
+                return requestQueue;
+            }
+        };
+        collector.stopHarvester();
+        
+        verify(request).setParameter(HarvesterCommand.class.getName(), HarvesterCommand.STOP.name());
+        verify(request).setParameter(HarvesterCommand.VM_ID.name(), "00101010");
+        
+        verify(requestQueue).putRequest(request);
+    }    
+}
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Fri Aug 31 11:26:14 2012 +0200
@@ -58,11 +58,11 @@
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
 import com.redhat.thermostat.thread.client.common.ThreadView.ThreadAction;
 import com.redhat.thermostat.thread.client.common.chart.LivingDaemonThreadDifferenceChart;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
 
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
-import com.redhat.thermostat.thread.collector.ThreadSummary;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class ThreadInformationController implements VmInformationServiceController {
 
@@ -75,16 +75,12 @@
     private Timer timer;
     private ApplicationCache cache;
     
-    String CACHE_RECORDING_KEY = "thread-live-recording-";
-
     private LivingDaemonThreadDifferenceChart model;
-    
+        
     public ThreadInformationController(VmRef ref, ApplicationService appService,
                                        ThreadCollectorFactory collectorFactory, 
                                        ThreadViewProvider viewFactory)
-    {
-        CACHE_RECORDING_KEY = CACHE_RECORDING_KEY + ref.getStringID();
-        
+    {        
         this.appService = appService;
         cache = appService.getApplicationCache();
 
@@ -115,7 +111,7 @@
                     timer.stop();
                     break;
                 
-                case VISIBLE:                    
+                case VISIBLE:
                     timer.start();
                     break;
 
@@ -125,17 +121,13 @@
             }
         });
         
-        view.setRecording(isRecording());
+        view.setRecording(isRecording(), false);
         view.addThreadActionListener(new ThreadActionListener());
     }
     
     private boolean isRecording() {
-        Boolean isRecording = (Boolean) cache.getAttribute(CACHE_RECORDING_KEY);
-        return (isRecording != null && isRecording); 
-    }
-    
-    private void setRecording(boolean recording) {
-        cache.addAttribute(CACHE_RECORDING_KEY, recording);
+        
+        return collector.isHarvesterCollecting();
     }
     
     @Override
@@ -174,15 +166,24 @@
 
         @Override
         public void actionPerformed(ActionEvent<ThreadAction> actionEvent) {
+
+            boolean result = false;
+            
             switch (actionEvent.getActionId()) {
             case START_LIVE_RECORDING:
-                collector.startHarvester();
-                setRecording(true);
+                result = collector.startHarvester();
+                if (!result) {
+                    view.displayWarning("Cannot enable Thread recording");
+                    view.setRecording(false, false);
+                }
                 break;
             
             case STOP_LIVE_RECORDING:
-                collector.stopHarvester();
-                setRecording(false);
+                result = collector.stopHarvester();
+                if (!result) {
+                    view.displayWarning("Cannot disable Thread recording");
+                    view.setRecording(true, false);
+                }
                 break;
                 
             default:
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationService.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationService.java	Fri Aug 31 11:26:14 2012 +0200
@@ -43,7 +43,7 @@
 import com.redhat.thermostat.client.osgi.service.VmInformationServiceController;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
 
 public class ThreadInformationService implements VmInformationService {
 
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java	Fri Aug 31 11:26:14 2012 +0200
@@ -50,8 +50,8 @@
 import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.thread.client.common.ThreadTableBean;
 import com.redhat.thermostat.thread.client.common.ThreadTableView;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadInfo;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
 
 public class ThreadTableController implements CommonController {
     
@@ -99,7 +99,7 @@
     private class ThreadTableControllerAction implements Runnable {
         @Override
         public void run() {
-            List<ThreadInfo> infos = collector.getThreadInfo();
+            List<ThreadInfoData> infos = collector.getThreadInfo();
             if(infos.size() > 0) {
                 
                 long lastPollTimestamp = infos.get(0).getTimeStamp();
@@ -110,11 +110,11 @@
                 // first, get a map of all threads with the respective info
                 // the list will contain an ordered-by-timestamp list
                 // with the known history for each thread
-                Map<ThreadInfo, List<ThreadInfo>> stats = new HashMap<>();
-                for (ThreadInfo info : infos) {
-                    List<ThreadInfo> beanList = stats.get(info);
+                Map<ThreadInfoData, List<ThreadInfoData>> stats = new HashMap<>();
+                for (ThreadInfoData info : infos) {
+                    List<ThreadInfoData> beanList = stats.get(info);
                     if (beanList == null) {
-                        beanList = new ArrayList<ThreadInfo>();
+                        beanList = new ArrayList<ThreadInfoData>();
                         stats.put(info, beanList);
                     }                    
                     beanList.add(info);
@@ -123,7 +123,7 @@
                 List<ThreadTableBean> tableBeans = new ArrayList<>();
                 
                 // now we have the list, we can do all the analysis we need
-                for (ThreadInfo key : stats.keySet()) {
+                for (ThreadInfoData key : stats.keySet()) {
                     ThreadTableBean bean = new ThreadTableBean();
                     
                     bean.setName(key.getName());
@@ -133,7 +133,7 @@
                     bean.setBlockedCount(key.getBlockedCount());
                     
                     // get start time and stop time, if any
-                    List<ThreadInfo> beanList = stats.get(key);
+                    List<ThreadInfoData> beanList = stats.get(key);
                     long last = beanList.get(0).getTimeStamp();
                     long first = beanList.get(beanList.size() - 1).getTimeStamp();
                     
@@ -146,7 +146,7 @@
                     // time for some stats
                     double running = 0;
                     double waiting = 0;
-                    for (ThreadInfo info : beanList) {
+                    for (ThreadInfoData info : beanList) {
                         State state = info.getState();
                         switch (state) {
                         case RUNNABLE:
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java	Fri Aug 31 11:26:14 2012 +0200
@@ -40,8 +40,8 @@
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class VMThreadCapabilitiesController implements CommonController {
 
@@ -61,7 +61,9 @@
                 switch (actionEvent.getActionId()) {
                 case VISIBLE:
                     VMThreadCapabilities caps = collector.getVMThreadCapabilities();
-                    view.setVMThreadCapabilities(caps);
+                    if (caps != null) {
+                        view.setVMThreadCapabilities(caps);
+                    }
                     break;
 
                 default:
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/osgi/Activator.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/osgi/Activator.java	Fri Aug 31 11:26:14 2012 +0200
@@ -46,8 +46,8 @@
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
 import com.redhat.thermostat.thread.client.controller.impl.ThreadInformationService;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
 
 public class Activator implements BundleActivator {
 
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -42,6 +42,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.times;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,8 +62,8 @@
 import com.redhat.thermostat.thread.client.common.ThreadView;
 import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
 
 public class ThreadInformationControllerTest {
 
@@ -74,7 +75,7 @@
     private ThreadInformationController controller;
     
     private ApplicationService appService;
-    
+        
     @Before
     public void setUp() {
         ApplicationContextUtil.resetApplicationContext();
@@ -125,13 +126,15 @@
         
         VmRef ref = mock(VmRef.class);
         
-        ThreadCollectorFactory collectorFactory = mock(ThreadCollectorFactory.class); 
+        ThreadCollectorFactory collectorFactory = mock(ThreadCollectorFactory.class);
+        ThreadCollector collector = mock(ThreadCollector.class);
+        when(collectorFactory.getCollector(ref)).thenReturn(collector);
         
         controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
     }
     
     @Test
-    public void liveRecodingKeySet() {
+    public void verifyLiveRecording() {
         
         ActionListener<ThreadView.ThreadAction> threadActionListener;
         ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
@@ -139,35 +142,39 @@
         
         VmRef ref = mock(VmRef.class);
         when(ref.getStringID()).thenReturn("42");
+                
+        ThreadCollector collector = mock(ThreadCollector.class);
+        when(collector.isHarvesterCollecting()).thenReturn(false).thenReturn(true);
+        when(collector.startHarvester()).thenReturn(true);
+        when(collector.stopHarvester()).thenReturn(true).thenReturn(false);
+
         ThreadCollectorFactory collectorFactory = mock(ThreadCollectorFactory.class);
-        
-        ThreadCollector collector = mock(ThreadCollector.class);
         when(collectorFactory.getCollector(ref)).thenReturn(collector);
         
         ApplicationCache cache = mock(ApplicationCache.class);
         appService = mock(ApplicationService.class);
         when(appService.getApplicationCache()).thenReturn(cache);
-        when(cache.getAttribute(anyString())).thenReturn(false).thenReturn(true);
         
         controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
-        assertEquals("thread-live-recording-42", controller.CACHE_RECORDING_KEY);
         
-        verify(cache).getAttribute("thread-live-recording-42");
-        verify(view).setRecording(false);
+        verify(collector).isHarvesterCollecting();
+        verify(view, times(1)).setRecording(false, false);
         
         threadActionListener = viewArgumentCaptor.getValue();
         threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.START_LIVE_RECORDING));
         
-        verify(cache).addAttribute("thread-live-recording-42", true);
+        verify(view, times(1)).setRecording(false, false);
         verify(collector).startHarvester();
         
         threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.STOP_LIVE_RECORDING));
-        verify(cache).addAttribute("thread-live-recording-42", false);
+        
         verify(collector).stopHarvester();        
+        verify(view, times(1)).setRecording(false, false);
         
-        // check that the value indeed persist across sessions
-        controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
-        verify(view).setRecording(true);
+        threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.STOP_LIVE_RECORDING));
+        
+        verify(collector, times(2)).stopHarvester();        
+        verify(view, times(1)).setRecording(true, false);
     }
     
     @Test
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -54,8 +54,8 @@
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
 import com.redhat.thermostat.thread.client.common.ThreadTableView;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadInfo;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
 
 public class ThreadTableControllerTest {
 
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesControllerTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesControllerTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -50,8 +50,8 @@
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class VMThreadCapabilitiesControllerTest {
 
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Fri Aug 31 11:26:14 2012 +0200
@@ -40,6 +40,7 @@
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
 import javax.swing.SwingUtilities;
@@ -65,6 +66,8 @@
     
     private static final Translate t = LocaleResources.createLocalizer();
 
+    private boolean skipNotification = false;
+    
     public SwingThreadView() {
         
         panel = new ThreadMainPanel();
@@ -92,6 +95,9 @@
         {
             @Override
             public void itemStateChanged(ItemEvent e) {
+                
+                if (skipNotification) return;
+                
                 ThreadAction action = null;
                 if (e.getStateChange() == ItemEvent.SELECTED) {
                     action = ThreadAction.START_LIVE_RECORDING;
@@ -130,11 +136,13 @@
     }
     
     @Override
-    public void setRecording(final boolean recording) {
+    public void setRecording(final boolean recording, final boolean notify) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
+                if (!notify) skipNotification = true;
                 timelinePanel.getRecordButton().setSelected(recording);
+                if (!notify) skipNotification = false;
             }
         });
     }
@@ -149,8 +157,13 @@
         });
     }
     
-    public void setLiveThreads(String liveThreads) {
-        timelinePanel.getLiveThreads().setText(liveThreads);
+    public void setLiveThreads(final String liveThreads) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                timelinePanel.getLiveThreads().setText(liveThreads);
+            }
+        });
     };
     
     @Override
@@ -179,4 +192,14 @@
     public ThreadTableView createThreadTableView() {
         return threadTable;
     }
+    
+    @Override
+    public void displayWarning(final String warning) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                JOptionPane.showMessageDialog(panel.getParent(), warning, "", JOptionPane.WARNING_MESSAGE);
+            }
+        });
+    }
 }
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingVMThreadCapabilitiesView.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingVMThreadCapabilitiesView.java	Fri Aug 31 11:26:14 2012 +0200
@@ -44,7 +44,7 @@
 import com.redhat.thermostat.client.ui.ComponentVisibleListener;
 import com.redhat.thermostat.client.ui.SwingComponent;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class SwingVMThreadCapabilitiesView extends VMThreadCapabilitiesView implements SwingComponent {
 
--- a/thread/collector/pom.xml	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/collector/pom.xml	Fri Aug 31 11:26:14 2012 +0200
@@ -66,6 +66,16 @@
       <artifactId>thermostat-common-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-agent-command</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-command</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     
     <dependency>
       <groupId>org.osgi</groupId>
@@ -95,17 +105,16 @@
         <configuration>
           <instructions>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.thread.collector.osgi.Activator</Bundle-Activator>
+            <Bundle-Activator>com.redhat.thermostat.thread.common.osgi.Activator</Bundle-Activator>
             <Bundle-SymbolicName>com.redhat.thermostat.thread.collector</Bundle-SymbolicName>
             <Export-Package>
               com.redhat.thermostat.thread.collector,
               com.redhat.thermostat.thread.dao,
+              com.redhat.thermostat.thread.model,
             </Export-Package>
             <Private-Package>
-              com.redhat.thermostat.thread.collector.osgi,
-              com.redhat.thermostat.thread.collector.impl,
+              com.redhat.thermostat.thread.common.osgi,
               com.redhat.thermostat.thread.dao.impl,
-              com.redhat.thermostat.utils.management,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/HarvesterCommand.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,15 @@
+package com.redhat.thermostat.thread.collector;
+
+public enum HarvesterCommand {
+
+    START,
+    STOP,
+    VM_CAPS,
+    IS_COLLECTING,
+    
+    AGENT_ID,
+    VM_ID;
+
+    public static final String RECEIVER = "com.redhat.thermostat.thread.harvester.ThreadHarvester";
+
+}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadCollector.java	Fri Aug 31 11:22:52 2012 +0200
+++ /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.thread.collector;
-
-import java.util.List;
-
-public interface ThreadCollector {
-    
-    VMThreadCapabilities getVMThreadCapabilities();
-    
-    void startHarvester();
-    void stopHarvester();
-
-    ThreadSummary getLatestThreadSummary();
-    List<ThreadSummary> getThreadSummary(long since);
-    List<ThreadSummary> getThreadSummary();
-    
-    /**
-     * Return a list of {@link ThreadInfo}, sorted in descending order their by
-     * {@link ThreadInfo#getTimeStamp()}, whose elements are at most
-     * "{@code since}" old.
-     */
-    List<ThreadInfo> getThreadInfo(long since);
-
-    /**
-     * Return a list of all the {@link ThreadInfo} collected, sorted in
-     * descending order their by {@link ThreadInfo#getTimeStamp()}.
-     */
-    List<ThreadInfo> getThreadInfo();
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadCollectorFactory.java	Fri Aug 31 11:22:52 2012 +0200
+++ /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.thread.collector;
-
-import com.redhat.thermostat.common.dao.VmRef;
-
-public interface ThreadCollectorFactory {
-
-    ThreadCollector getCollector(VmRef reference);
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadInfo.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.collector;
-
-import java.lang.Thread.State;
-
-import com.redhat.thermostat.common.model.TimeStampedPojo;
-
-public interface ThreadInfo extends TimeStampedPojo {
-
-    StackTraceElement[] getStackTrace();
-
-    String getName();
-
-    long getAllocatedBytes();
-
-    long getThreadID();
-
-    State getState();
-
-    long getBlockedCount();
-
-    long getWaitedCount();
-
-    long getCpuTime();
-
-    long getUserTime();
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/ThreadSummary.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.collector;
-
-import com.redhat.thermostat.common.model.TimeStampedPojo;
-
-public interface ThreadSummary extends TimeStampedPojo {
-
-    /**
-     * Represents the number of living {@link Thread}s, including daemon
-     * {@link Thread}s, currently running.
-     * 
-     * <br /><br />
-     * 
-     * A {@link Thread} is alive if it has been created, started and is not
-     * dead.
-     * 
-     * @see Thread#isAlive()
-     * @see Thread.State
-     */
-    long currentLiveThreads();
-    
-    /**
-     * Represents the number of living {@link Thread}s which are also daemon
-     * {@link Thread}s, currently running.
-     * 
-     * <br /><br />
-     * 
-     * A {@link Thread} is alive if it has been created, started and is not
-     * dead.
-     * 
-     * @see #currentLiveThreads()
-     * @see Thread#isAlive()
-     * @see Thread.State
-     */
-    long currentDaemonThreads();
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/VMThreadCapabilities.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +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.thread.collector;
-
-import java.util.List;
-
-public interface VMThreadCapabilities {
-
-    boolean supportCPUTime();
-    boolean supportContentionMonitor();
-    boolean supportThreadAllocatedMemory();
-    
-    List<String> getSupportedFeaturesList();
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadCollectorFactoryImpl.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +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.thread.collector.impl;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
-
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-import com.redhat.thermostat.utils.management.MXBeanConnector;
-
-public class ThreadCollectorFactoryImpl implements ThreadCollectorFactory {
-
-    private Map<VmRef, ThreadMXBeanCollector> collectors;
-    
-    private ThreadDao threadDao;
-    private ScheduledExecutorService threadPool;
-    
-    public ThreadCollectorFactoryImpl(ThreadDao threadDao, ScheduledExecutorService threadPool) {
-        this.threadDao = threadDao;
-        this.threadPool = threadPool;
-        
-        collectors = new HashMap<VmRef, ThreadMXBeanCollector>();
-    }
-    
-    @Override
-    public synchronized ThreadCollector getCollector(VmRef reference) {
-        ThreadMXBeanCollector collector = collectors.get(reference);
-        if (collector == null) {
-            collector = new ThreadMXBeanCollector(threadDao, reference, new MXBeanConnector(reference), threadPool);
-            collectors.put(reference, collector);
-        }
-        return collector;
-    }
-    
-    public void shutdown() {
-        for (ThreadMXBeanCollector collector : collectors.values()) {
-            collector.stopHarvester();
-        }
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXBeanCollector.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +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.thread.collector.impl;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import javax.management.MalformedObjectNameException;
-
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.thread.collector.ThreadCollector;
-import com.redhat.thermostat.thread.collector.ThreadSummary;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-import com.redhat.thermostat.utils.management.MXBeanConnection;
-import com.redhat.thermostat.utils.management.MXBeanConnector;
-
-@SuppressWarnings("restriction")
-public class ThreadMXBeanCollector implements ThreadCollector {
-
-    private ThreadDao threadDao;
-    private VmRef ref;
-    private MXBeanConnector connector;
-
-    private MXBeanConnection connection;
-    private ThreadMXBean collectorBean;
-    
-    private boolean isConnected;
-    private ScheduledExecutorService threadPool;
-    
-    private ScheduledFuture<?> harvester;
-    
-    public ThreadMXBeanCollector(ThreadDao threadDao, VmRef ref, MXBeanConnector connector, ScheduledExecutorService threadPool) {
-        this.threadDao = threadDao;
-        this.ref = ref;
-        this.connector = connector;
-        this.threadPool = threadPool;
-    }
-
-    @Override
-    public synchronized void startHarvester() {
-        if (isConnected) return;
-        
-        if (!connector.isAttached()) {
-            try {
-                connector.attach();
-            } catch (Exception ignore) {
-                ignore.printStackTrace();
-            }
-        }
-        
-        try {
-            connection = connector.connect();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        isConnected = true;
-        
-        harvester = threadPool.scheduleAtFixedRate(new Harvester(), 0, 1, TimeUnit.SECONDS);
-    }
-    
-    private class Harvester implements Runnable {
-        @Override
-        public void run() {
-            harvestData();
-        }
-    }
-    
-    @Override
-    public synchronized void stopHarvester() {
-        if (!isConnected) return;
-        
-        harvester.cancel(false);
-        
-        try {
-            connection.close();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        
-        if (connector.isAttached()) {
-            try {
-                connector.close();
-            } catch (Exception ignore) {
-                ignore.printStackTrace();
-            }
-        }
-        
-        isConnected = false;
-    }
-    
-    @Override
-    public ThreadSummary getLatestThreadSummary() {
-        ThreadSummary summary = threadDao.loadLastestSummary(ref);
-        if (summary == null) {
-            // default to all 0
-            summary = new ThreadMXSummary();
-        }
-        return summary;
-    }
-    
-    @Override
-    public List<ThreadSummary> getThreadSummary(long since) {
-        List<ThreadSummary> summary = threadDao.loadSummary(ref, since);
-        return summary;
-    }
-    
-    @Override
-    public List<ThreadSummary> getThreadSummary() {
-        return getThreadSummary(0);
-    }
-    
-    @Override
-    public List<com.redhat.thermostat.thread.collector.ThreadInfo> getThreadInfo() {
-        return getThreadInfo(0);
-    }
-    
-    @Override
-    public List<com.redhat.thermostat.thread.collector.ThreadInfo> getThreadInfo(long since) {
-        return threadDao.loadThreadInfo(ref, since);
-    }
-    
-    public synchronized void harvestData() {
-        try {
-            
-            long timestamp = System.currentTimeMillis();
-            
-            ThreadMXSummary summary = new ThreadMXSummary();
-            
-            collectorBean = getDataCollectorBean(connection);
-            
-            summary.setCurrentLiveThreads(collectorBean.getThreadCount());
-            summary.setDaemonThreads(collectorBean.getDaemonThreadCount());
-            summary.setTimestamp(timestamp);
-            
-            threadDao.saveSummary(ref, summary);
-            
-            long [] ids = collectorBean.getAllThreadIds();
-            long[] allocatedBytes = null;
-            
-            // now the details for the threads
-            if (collectorBean instanceof com.sun.management.ThreadMXBean) {
-                com.sun.management.ThreadMXBean sunBean = (com.sun.management.ThreadMXBean) collectorBean;
-                boolean wasEnabled = false;
-                if (sunBean.isThreadAllocatedMemorySupported()) {
-                    wasEnabled = sunBean.isThreadAllocatedMemoryEnabled();
-                    sunBean.setThreadAllocatedMemoryEnabled(true);
-                    allocatedBytes = sunBean.getThreadAllocatedBytes(ids);
-                    sunBean.setThreadAllocatedMemoryEnabled(wasEnabled);
-                }
-            }
-
-            ThreadInfo[] threadInfos = collectorBean.getThreadInfo(ids, true, true);
-            
-            for (int i = 0; i < ids.length; i++) {
-                ThreadMXInfo info = new ThreadMXInfo();
-                ThreadInfo beanInfo = threadInfos[i];
-
-                info.setTimeStamp(timestamp);
-
-                info.setName(beanInfo.getThreadName());
-                info.setID(beanInfo.getThreadId());
-                info.setState(beanInfo.getThreadState());
-                info.setStackTrace(beanInfo.getStackTrace());
-
-                info.setCPUTime(collectorBean.getThreadCpuTime(info.getThreadID()));
-                info.setUserTime(collectorBean.getThreadUserTime(info.getThreadID()));
-                
-                info.setBlockedCount(beanInfo.getBlockedCount());
-                info.setWaitedCount(beanInfo.getWaitedCount());
-                
-                if (allocatedBytes != null) {
-                    info.setAllocatedBytes(allocatedBytes[i]);
-                }
-
-                threadDao.saveThreadInfo(ref, info);
-            }
-            
-        } catch (MalformedObjectNameException e) {
-            e.printStackTrace();
-        }
-    }
-    
-    @Override
-    public synchronized VMThreadCapabilities getVMThreadCapabilities() {
-        
-        VMThreadCapabilities caps = threadDao.loadCapabilities(ref);
-        if (caps == null) {
-            if (!connector.isAttached()) {
-                try {
-                    connector.attach();
-                } catch (Exception ignore) {
-                    ignore.printStackTrace();
-                }
-            }
-            
-            // query caps to the vm, then save for later
-            try (MXBeanConnection connection = connector.connect()) {
-                             
-                ThreadMXBean bean = getDataCollectorBean(connection);
-                VMThreadMXCapabilities _caps = new VMThreadMXCapabilities();
-                
-                if (bean.isThreadCpuTimeSupported()) _caps.addFeature(ThreadDao.CPU_TIME);
-                if (bean.isThreadContentionMonitoringSupported()) _caps.addFeature(ThreadDao.CONTENTION_MONITOR);
-                
-                if (bean instanceof com.sun.management.ThreadMXBean) {
-                    com.sun.management.ThreadMXBean sunBean = (com.sun.management.ThreadMXBean) bean;
-                    if (sunBean.isThreadAllocatedMemorySupported())
-                        _caps.addFeature(ThreadDao.THREAD_ALLOCATED_MEMORY);
-                }
-                
-                caps = _caps;
-                
-                threadDao.saveCapabilities(ref, caps);
-                
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-
-            if (connector.isAttached()) {
-                try {
-                    connector.close();
-                } catch (Exception ignore) {
-                    ignore.printStackTrace();
-                }
-            }
-        }
-        
-        return caps;
-    }
-    
-    private ThreadMXBean getDataCollectorBean(MXBeanConnection connection) throws MalformedObjectNameException {
-        ThreadMXBean bean = null;
-        try {
-            bean = connection.createProxy(ManagementFactory.THREAD_MXBEAN_NAME,
-                                          com.sun.management.ThreadMXBean.class);
-        } catch (MalformedObjectNameException ignore) {}
-        
-        if (bean == null) {
-            bean = connection.createProxy(ManagementFactory.THREAD_MXBEAN_NAME,
-                                          ThreadMXBean.class);
-        }
-        return bean;
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXInfo.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +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.thread.collector.impl;
-
-import java.lang.Thread.State;
-import java.util.Arrays;
-
-import com.redhat.thermostat.thread.collector.ThreadInfo;
-
-public class ThreadMXInfo implements ThreadInfo {
-
-    private StackTraceElement[] stackTrace;
-    private long threadID;
-    private State threadState;
-    private String name;
-    private long allocatedBytes;
-    
-    private long threadCpuTime;
-    private long threadUserTime;
-    private long blockedCount;
-    private long waitedCount;
-    
-    private long timestamp;
-    
-    public void setStackTrace(StackTraceElement[] stackTrace) {
-        this.stackTrace = stackTrace;
-    }
-    
-    @Override
-    public StackTraceElement[] getStackTrace() {
-        return stackTrace;
-    }
-    
-    @Override
-    public String toString() {
-        return "ThreadMXInfo [name=" + name
-                + ", threadID=" + threadID + ", threadState=" + threadState
-                + ", stackTrace=" + Arrays.toString(stackTrace)
-                + ", allocatedBytes=" + allocatedBytes
-                + ", threadCpuTime=" + threadCpuTime + ", threadUserTime="
-                + threadUserTime + ", blockedCount=" + blockedCount
-                + ", waitedCount=" + waitedCount + ", timestamp=" + timestamp
-                + "]";
-    }
-
-    public void setName(String threadName) {
-        this.name = threadName;
-    }
-
-    public void setID(long threadID) {
-        this.threadID = threadID;
-    }
-
-    public void setState(State threadState) {
-        this.threadState = threadState;
-    }
-
-    public void setAllocatedBytes(long allocatedBytes) {
-        this.allocatedBytes = allocatedBytes;
-    }
-
-    @Override
-    public String getName() {
-        return name;
-    }
-    
-    @Override
-    public long getAllocatedBytes() {
-        return allocatedBytes;
-    }
-    
-    @Override
-    public long getThreadID() {
-        return threadID;
-    }
-    
-    @Override
-    public State getState() {
-        return threadState;
-    }
-    
-    @Override
-    public long getTimeStamp() {
-        return timestamp;
-    }
-    
-    public void setTimeStamp(long timestamp) {
-        this.timestamp = timestamp;
-    }
-
-    public void setCPUTime(long threadCpuTime) {
-        this.threadCpuTime = threadCpuTime;
-    }
-
-    public void setUserTime(long threadUserTime) {
-       this.threadUserTime = threadUserTime;
-    }
-
-    public void setBlockedCount(long blockedCount) {
-        this.blockedCount = blockedCount;
-    }
-
-    public void setWaitedCount(long waitedCount) {
-        this.waitedCount = waitedCount;
-    }
-    
-    @Override
-    public long getBlockedCount() {
-        return blockedCount;
-    }
-    
-    @Override
-    public long getWaitedCount() {
-        return waitedCount;
-    }
-    
-    @Override
-    public long getCpuTime() {
-        return threadCpuTime;
-    }
-    
-    @Override
-    public long getUserTime() {
-        return threadUserTime;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((name == null) ? 0 : name.hashCode());
-        result = prime * result + (int) (threadID ^ (threadID >>> 32));
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        ThreadMXInfo other = (ThreadMXInfo) obj;
-        if (name == null) {
-            if (other.name != null)
-                return false;
-        } else if (!name.equals(other.name))
-            return false;
-        if (threadID != other.threadID)
-            return false;
-        return true;
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/ThreadMXSummary.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.collector.impl;
-
-import com.redhat.thermostat.thread.collector.ThreadSummary;
-
-public class ThreadMXSummary implements ThreadSummary {
-
-    private long currentLiveThreads;
-    private long daemonThreads;
-    
-    private long timestamp;
-    
-    @Override
-    public long currentLiveThreads() {
-        return currentLiveThreads;
-    }
-    
-    public void setCurrentLiveThreads(long currentLiveThreads) {
-        this.currentLiveThreads = currentLiveThreads;
-    }
-    
-    @Override
-    public long currentDaemonThreads() {
-        return daemonThreads;
-    }
-    
-    public void setDaemonThreads(long daemonThreads) {
-        this.daemonThreads = daemonThreads;
-    }
-    
-    @Override
-    public long getTimeStamp() {
-        return timestamp;
-    }
-    
-    public void setTimestamp(long timestamp) {
-        this.timestamp = timestamp;
-    }
-    
-    @Override
-    public String toString() {
-        return "[timestamp: " + timestamp + ", currentLiveThreads: " +
-               currentLiveThreads + ", daemonThreads: " + daemonThreads + "]";
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/impl/VMThreadMXCapabilities.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.thread.collector.impl;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import com.redhat.thermostat.common.storage.Key;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-
-public class VMThreadMXCapabilities implements VMThreadCapabilities {
-    
-    private Set<String> features = new HashSet<>();
-    
-    @Override
-    public boolean supportCPUTime() {
-        return features.contains(ThreadDao.CPU_TIME);
-    }
-      
-    @Override
-    public boolean supportContentionMonitor() {
-        return features.contains(ThreadDao.CONTENTION_MONITOR);
-    }
-    
-    @Override
-    public String toString() {
-        return "[supportCPU: " + supportCPUTime() + ", supportContention: " + supportContentionMonitor() +
-               ", supportThreadAllocatedMemory: " + supportThreadAllocatedMemory() + "]";
-    }
-    
-    @Override
-    public boolean supportThreadAllocatedMemory() {
-        return features.contains(ThreadDao.THREAD_ALLOCATED_MEMORY);
-    }
-    
-    @Override
-    public List<String> getSupportedFeaturesList() {
-        return new ArrayList<>(features);
-    }
-    
-    public void addFeature(String feature) {
-        features.add(feature);
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/collector/osgi/Activator.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +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.thread.collector.osgi;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
-import com.redhat.thermostat.thread.collector.impl.ThreadCollectorFactoryImpl;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-import com.redhat.thermostat.thread.dao.impl.ThreadDaoImpl;
-
-public class Activator implements BundleActivator {
-    
-    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(24);
-    private ThreadCollectorFactoryImpl collectorFactory;
-    
-    @Override
-    public void start(final BundleContext context) throws Exception {
-        @SuppressWarnings({ "rawtypes", "unchecked" })
-        ServiceTracker tracker = new ServiceTracker(context, Storage.class.getName(), null) {
-            @Override
-            public Object addingService(ServiceReference reference) {
-                
-                Storage storage = (Storage) context.getService(reference);
-                
-                ThreadDao threadDao = new ThreadDaoImpl(storage);
-                collectorFactory = new ThreadCollectorFactoryImpl(threadDao, executor);
-
-                context.registerService(ThreadCollectorFactory.class.getName(), collectorFactory, null);
-                
-                return super.addingService(reference);
-            }
-        };
-        tracker.open();
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (collectorFactory != null) {
-            collectorFactory.shutdown();
-        }
-        if (executor != null) {
-            executor.shutdown();
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/common/osgi/Activator.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,78 @@
+/*
+ * 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.thread.common.osgi;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.dao.impl.ThreadDaoImpl;
+
+public class Activator implements BundleActivator {
+
+    @SuppressWarnings("rawtypes")
+    private ServiceRegistration reg;
+
+    @Override
+    public void start(BundleContext context) throws Exception {
+
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        ServiceTracker tracker = new ServiceTracker(context, Storage.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                Storage storage = (Storage) context.getService(reference);
+                ThreadDao threadDao = new ThreadDaoImpl(storage);
+                reg = context.registerService(ThreadDao.class.getName(), threadDao, null);
+                return super.addingService(reference);
+            }
+        };
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        if (reg != null) {
+            reg.unregister();
+        }
+    }
+
+}
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Fri Aug 31 11:26:14 2012 +0200
@@ -41,9 +41,10 @@
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Key;
-import com.redhat.thermostat.thread.collector.ThreadInfo;
-import com.redhat.thermostat.thread.collector.ThreadSummary;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+import com.redhat.thermostat.common.storage.Storage;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public interface ThreadDao {
 
@@ -62,7 +63,7 @@
 
 
     VMThreadCapabilities loadCapabilities(VmRef ref);
-    void saveCapabilities(VmRef ref, VMThreadCapabilities caps);
+    void saveCapabilities(String vmId, String agentId, VMThreadCapabilities caps);
 
     static final String LIVE_THREADS = "thread-living";
     static final Key<Long> LIVE_THREADS_KEY = new Key<Long>(LIVE_THREADS, false);
@@ -74,7 +75,7 @@
                          Key.TIMESTAMP,
                          LIVE_THREADS_KEY, DAEMON_THREADS_KEY);
     
-    void saveSummary(VmRef vm, ThreadSummary summary);
+    void saveSummary(String vmId, String agentId, ThreadSummary summary);
     ThreadSummary loadLastestSummary(VmRef ref);
     List<ThreadSummary> loadSummary(VmRef ref, long since);
 
@@ -104,6 +105,8 @@
                          THREAD_USER_TIME_KEY, THREAD_BLOCKED_COUNT_KEY,
                          THREAD_WAIT_COUNT_KEY, THREAD_STACK_TRACE_ID_KEY);
     
-    void saveThreadInfo(VmRef ref, ThreadInfo info);
-    List<ThreadInfo> loadThreadInfo(VmRef ref, long since);
+    void saveThreadInfo(String vmId, String agentId, ThreadInfoData info);
+    List<ThreadInfoData> loadThreadInfo(VmRef ref, long since);
+    
+    Storage getStorage();
 }
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Fri Aug 31 11:26:14 2012 +0200
@@ -45,13 +45,10 @@
 import com.redhat.thermostat.common.storage.Cursor;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.thread.collector.ThreadInfo;
-import com.redhat.thermostat.thread.collector.ThreadSummary;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
-import com.redhat.thermostat.thread.collector.impl.ThreadMXInfo;
-import com.redhat.thermostat.thread.collector.impl.ThreadMXSummary;
-import com.redhat.thermostat.thread.collector.impl.VMThreadMXCapabilities;
 import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class ThreadDaoImpl implements ThreadDao {
 
@@ -66,7 +63,7 @@
     @Override
     public VMThreadCapabilities loadCapabilities(VmRef vm) {
         
-        VMThreadMXCapabilities caps = null;
+        VMThreadCapabilities caps = null;
         
         Chunk query = new Chunk(THREAD_CAPABILITIES, false);
         query.put(Key.VM_ID, vm.getId());
@@ -74,7 +71,7 @@
         
         Chunk found = storage.find(query);
         if (found != null) {
-            caps = new VMThreadMXCapabilities();
+            caps = new VMThreadCapabilities();
             if (found.get(CONTENTION_MONITOR_KEY)) caps.addFeature(CONTENTION_MONITOR);
             if (found.get(CPU_TIME_KEY)) caps.addFeature(CPU_TIME);
             if (found.get(THREAD_ALLOCATED_MEMORY_KEY)) caps.addFeature(THREAD_ALLOCATED_MEMORY);
@@ -84,8 +81,8 @@
     }
     
     @Override
-    public void saveCapabilities(VmRef vm, VMThreadCapabilities caps) {
-        Chunk chunk = prepareChunk(THREAD_CAPABILITIES, true, vm);
+    public void saveCapabilities(String vmId, String agentId, VMThreadCapabilities caps) {
+        Chunk chunk = prepareChunk(THREAD_CAPABILITIES, true, vmId, agentId);
         
         chunk.put(CONTENTION_MONITOR_KEY, caps.supportContentionMonitor());
         chunk.put(CPU_TIME_KEY, caps.supportCPUTime());
@@ -95,8 +92,8 @@
     }
     
     @Override
-    public void saveSummary(VmRef vm, ThreadSummary summary) {
-        Chunk chunk = prepareChunk(THREAD_SUMMARY, false, vm);
+    public void saveSummary(String vmId, String agentId, ThreadSummary summary) {
+        Chunk chunk = prepareChunk(THREAD_SUMMARY, false, vmId, agentId);
         
         chunk.put(LIVE_THREADS_KEY, summary.currentLiveThreads());
         chunk.put(DAEMON_THREADS_KEY, summary.currentDaemonThreads());
@@ -107,13 +104,13 @@
     
     @Override
     public ThreadSummary loadLastestSummary(VmRef ref) {
-        ThreadMXSummary summary = null;
+        ThreadSummary summary = null;
 
         Chunk query = prepareChunk(THREAD_SUMMARY, false, ref);
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING).limit(1);
         if (cursor.hasNext()) {
             Chunk found = cursor.next();
-            summary = new ThreadMXSummary();
+            summary = new ThreadSummary();
             summary.setTimestamp(found.get(Key.TIMESTAMP));
             summary.setCurrentLiveThreads(found.get(LIVE_THREADS_KEY));
             summary.setDaemonThreads(found.get(DAEMON_THREADS_KEY));
@@ -132,7 +129,7 @@
 
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
         while (cursor.hasNext()) {
-            ThreadMXSummary summary = new ThreadMXSummary();
+            ThreadSummary summary = new ThreadSummary();
             
             Chunk found = cursor.next();
             summary.setTimestamp(found.get(Key.TIMESTAMP));
@@ -145,8 +142,8 @@
     }
     
     @Override
-    public void saveThreadInfo(VmRef ref, ThreadInfo info) {
-        Chunk chunk = prepareChunk(THREAD_INFO, false, ref);
+    public void saveThreadInfo(String vmId, String agentId, ThreadInfoData info) {
+        Chunk chunk = prepareChunk(THREAD_INFO, false, vmId, agentId);
         
         chunk.put(Key.TIMESTAMP, info.getTimeStamp());
 
@@ -163,15 +160,15 @@
     }
 
     @Override
-    public List<ThreadInfo> loadThreadInfo(VmRef ref, long since) {
-        List<ThreadInfo> result = new ArrayList<>();
+    public List<ThreadInfoData> loadThreadInfo(VmRef ref, long since) {
+        List<ThreadInfoData> result = new ArrayList<>();
         
         Chunk query = prepareChunk(THREAD_INFO, false, ref);
         query.put(Key.WHERE, "this.timestamp > " + since);
         
         Cursor cursor = storage.findAll(query).sort(Key.TIMESTAMP, Cursor.SortDirection.DESCENDING);
         while (cursor.hasNext()) {
-            ThreadMXInfo info = new ThreadMXInfo();
+            ThreadInfoData info = new ThreadInfoData();
             
             Chunk found = cursor.next();
             info.setTimeStamp(found.get(Key.TIMESTAMP));
@@ -191,10 +188,19 @@
         return result;
     }
     
-    private Chunk prepareChunk(Category category, boolean replace, VmRef vm) {
+    private Chunk prepareChunk(Category category, boolean replace, String vmId, String agentId) {
         Chunk chunk = new Chunk(category, replace);
-        chunk.put(Key.AGENT_ID, vm.getAgent().getAgentId());
-        chunk.put(Key.VM_ID, vm.getId());
+        chunk.put(Key.AGENT_ID, agentId);
+        chunk.put(Key.VM_ID, Integer.valueOf(vmId));
         return chunk;
     }
+    
+    private Chunk prepareChunk(Category category, boolean replace, VmRef vm) {
+        return prepareChunk(category, replace, vm.getIdString(), vm.getAgent().getAgentId());
+    }
+    
+    @Override
+    public Storage getStorage() {
+        return storage;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/model/ThreadInfoData.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,178 @@
+/*
+ * 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.thread.model;
+
+import java.lang.Thread.State;
+import java.util.Arrays;
+
+import com.redhat.thermostat.common.model.TimeStampedPojo;
+
+
+public class ThreadInfoData implements TimeStampedPojo {
+
+    private StackTraceElement[] stackTrace;
+    private long threadID;
+    private State threadState;
+    private String name;
+    private long allocatedBytes;
+    
+    private long threadCpuTime;
+    private long threadUserTime;
+    private long blockedCount;
+    private long waitedCount;
+    
+    private long timestamp;
+    
+    public void setStackTrace(StackTraceElement[] stackTrace) {
+        this.stackTrace = stackTrace;
+    }
+
+    public StackTraceElement[] getStackTrace() {
+        return stackTrace;
+    }
+    
+    @Override
+    public String toString() {
+        return "ThreadMXInfo [name=" + name
+                + ", threadID=" + threadID + ", threadState=" + threadState
+                + ", stackTrace=" + Arrays.toString(stackTrace)
+                + ", allocatedBytes=" + allocatedBytes
+                + ", threadCpuTime=" + threadCpuTime + ", threadUserTime="
+                + threadUserTime + ", blockedCount=" + blockedCount
+                + ", waitedCount=" + waitedCount + ", timestamp=" + timestamp
+                + "]";
+    }
+
+    public void setName(String threadName) {
+        this.name = threadName;
+    }
+
+    public void setID(long threadID) {
+        this.threadID = threadID;
+    }
+
+    public void setState(State threadState) {
+        this.threadState = threadState;
+    }
+
+    public void setAllocatedBytes(long allocatedBytes) {
+        this.allocatedBytes = allocatedBytes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public long getAllocatedBytes() {
+        return allocatedBytes;
+    }
+
+    public long getThreadID() {
+        return threadID;
+    }
+
+    public State getState() {
+        return threadState;
+    }
+
+    public long getTimeStamp() {
+        return timestamp;
+    }
+    
+    public void setTimeStamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public void setCPUTime(long threadCpuTime) {
+        this.threadCpuTime = threadCpuTime;
+    }
+
+    public void setUserTime(long threadUserTime) {
+       this.threadUserTime = threadUserTime;
+    }
+
+    public void setBlockedCount(long blockedCount) {
+        this.blockedCount = blockedCount;
+    }
+
+    public void setWaitedCount(long waitedCount) {
+        this.waitedCount = waitedCount;
+    }
+
+    public long getBlockedCount() {
+        return blockedCount;
+    }
+
+    public long getWaitedCount() {
+        return waitedCount;
+    }
+
+    public long getCpuTime() {
+        return threadCpuTime;
+    }
+
+    public long getUserTime() {
+        return threadUserTime;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + (int) (threadID ^ (threadID >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ThreadInfoData other = (ThreadInfoData) obj;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (threadID != other.threadID)
+            return false;
+        return true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/model/ThreadSummary.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,78 @@
+/*
+ * 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.thread.model;
+
+import com.redhat.thermostat.common.model.TimeStampedPojo;
+
+
+public class ThreadSummary implements TimeStampedPojo {
+
+    private long currentLiveThreads;
+    private long daemonThreads;
+    
+    private long timestamp;
+
+    public long currentLiveThreads() {
+        return currentLiveThreads;
+    }
+    
+    public void setCurrentLiveThreads(long currentLiveThreads) {
+        this.currentLiveThreads = currentLiveThreads;
+    }
+
+    public long currentDaemonThreads() {
+        return daemonThreads;
+    }
+    
+    public void setDaemonThreads(long daemonThreads) {
+        this.daemonThreads = daemonThreads;
+    }
+
+    public long getTimeStamp() {
+        return timestamp;
+    }
+    
+    public void setTimestamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+    
+    @Override
+    public String toString() {
+        return "[timestamp: " + timestamp + ", currentLiveThreads: " +
+               currentLiveThreads + ", daemonThreads: " + daemonThreads + "]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/model/VMThreadCapabilities.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,74 @@
+/*
+ * 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.thread.model;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.redhat.thermostat.thread.dao.ThreadDao;
+
+public class VMThreadCapabilities {
+    
+    private Set<String> features = new HashSet<>();
+
+    public boolean supportCPUTime() {
+        return features.contains(ThreadDao.CPU_TIME);
+    }
+
+    public boolean supportContentionMonitor() {
+        return features.contains(ThreadDao.CONTENTION_MONITOR);
+    }
+
+    public String toString() {
+        return "[supportCPU: " + supportCPUTime() + ", supportContention: " + supportContentionMonitor() +
+               ", supportThreadAllocatedMemory: " + supportThreadAllocatedMemory() + "]";
+    }
+
+    public boolean supportThreadAllocatedMemory() {
+        return features.contains(ThreadDao.THREAD_ALLOCATED_MEMORY);
+    }
+
+    public List<String> getSupportedFeaturesList() {
+        return new ArrayList<>(features);
+    }
+    
+    public void addFeature(String feature) {
+        features.add(feature);
+    }
+}
--- a/thread/collector/src/main/java/com/redhat/thermostat/utils/management/MXBeanConnection.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.utils.management;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-import javax.management.JMX;
-import javax.management.MBeanServerConnection;
-import javax.management.MalformedObjectNameException;
-import javax.management.ObjectName;
-import javax.management.remote.JMXConnector;
-
-public class MXBeanConnection implements Closeable {
-
-    private JMXConnector connection;
-    private MBeanServerConnection mbsc;
-    
-    MXBeanConnection(JMXConnector connection, MBeanServerConnection mbsc) {
-        this.connection = connection;
-        this.mbsc = mbsc;
-    }
-    
-    public synchronized <E> E createProxy(String name, Class<? extends E> proxyClass) throws MalformedObjectNameException {
-        ObjectName objectName = new ObjectName(name);
-        return JMX.newMXBeanProxy(mbsc, objectName, proxyClass);
-    }
-    
-    @Override
-    public void close() throws IOException {
-        connection.close();
-    }
-}
--- a/thread/collector/src/main/java/com/redhat/thermostat/utils/management/MXBeanConnector.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +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.utils.management;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-
-import javax.management.MBeanServerConnection;
-import javax.management.remote.JMXConnector;
-import javax.management.remote.JMXConnectorFactory;
-import javax.management.remote.JMXServiceURL;
-
-import com.redhat.thermostat.common.dao.VmRef;
-import com.sun.tools.attach.VirtualMachine;
-
-public class MXBeanConnector implements Closeable {
-
-    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
-    private String connectorAddress;
-    
-    private VmRef reference;
-    private VirtualMachine vm;
-    
-    private boolean attached;
-    
-    public MXBeanConnector(VmRef reference) {
-        this.reference = reference;
-    }
-    
-    public synchronized void attach() throws Exception {
-        if (attached)
-            throw new IOException("Already attached");
-        
-        vm = VirtualMachine.attach(reference.getStringID());
-        attached = true;
-        
-        Properties props = vm.getAgentProperties();
-        connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-        if (connectorAddress == null) {
-           props = vm.getSystemProperties();
-           String home = props.getProperty("java.home");
-           String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
-           vm.loadAgent(agent);
-           
-           props = vm.getAgentProperties();
-           connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
-        }
-    }
-    
-    public synchronized MXBeanConnection connect() throws Exception {
-        
-        if (!attached)
-            throw new IOException("Agent not attached to target VM");
-        
-        JMXServiceURL url = new JMXServiceURL(connectorAddress);
-        JMXConnector connection = JMXConnectorFactory.connect(url);
-        MBeanServerConnection mbsc = null;
-        try {
-            mbsc = connection.getMBeanServerConnection();
-            
-        } catch (IOException e) {
-            connection.close();
-            throw e;
-        }
-        
-        return new MXBeanConnection(connection, mbsc);
-    }
-    
-    public boolean isAttached() {
-        return attached;
-    }
-    
-    @Override
-    public synchronized void close() throws IOException {
-        if (attached) {
-            vm.detach();
-            attached = false;
-        }
-    }
-}
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/collector/ThreadCollectorFactoryTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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.thread.collector;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
-
-import java.util.concurrent.ScheduledExecutorService;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.thread.collector.impl.ThreadCollectorFactoryImpl;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-
-public class ThreadCollectorFactoryTest {
-
-    @Test
-    public void testThreadCollectorFactory() {
-        ThreadDao threadDao = mock(ThreadDao.class);
-        VmRef reference = mock(VmRef.class);
-        
-        ScheduledExecutorService threadPool = mock(ScheduledExecutorService.class);
-        
-        ThreadCollectorFactory factory = new ThreadCollectorFactoryImpl(threadDao, threadPool);
-        ThreadCollector collector = factory.getCollector(reference);
-        assertNotNull(collector);
-        
-        // ask again, it must be the same instance as before
-        ThreadCollector collector2 = factory.getCollector(reference);
-        assertSame(collector, collector2);
-        
-        // and now of course it must be different
-        VmRef reference2 = mock(VmRef.class);
-        ThreadCollector collector3 = factory.getCollector(reference2);
-        assertNotSame(collector, collector3);
-    }
-}
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/collector/ThreadCollectorTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,204 +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.thread.collector;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-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.lang.management.ManagementFactory;
-import java.lang.management.ThreadMXBean;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.management.MalformedObjectNameException;
-
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.redhat.thermostat.common.dao.VmRef;
-import com.redhat.thermostat.thread.collector.impl.ThreadMXBeanCollector;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-import com.redhat.thermostat.utils.management.MXBeanConnection;
-import com.redhat.thermostat.utils.management.MXBeanConnector;
-
-public class ThreadCollectorTest {
-
-    @Test
-    public void testVMCapabilitiesNotInDAO() throws Exception {
-        
-        VMThreadCapabilities referenceCaps = mock(VMThreadCapabilities.class);
-        when(referenceCaps.supportContentionMonitor()).thenReturn(true);
-        when(referenceCaps.supportCPUTime()).thenReturn(false);
-        
-        VmRef reference = mock(VmRef.class);
-        
-        ThreadDao threadDao = mock(ThreadDao.class);
-        when(threadDao.loadCapabilities(reference)).thenReturn(null);
-        
-        MXBeanConnector connector = mock(MXBeanConnector.class);
-        when(connector.isAttached()).thenReturn(false).thenReturn(true);
-        
-        MXBeanConnection connection = mock(MXBeanConnection.class);
-        when(connector.connect()).thenReturn(connection);
-                
-        ArgumentCaptor<String> captor1 = ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<Class> captor2 = ArgumentCaptor.forClass(Class.class);
-        
-        ThreadMXBean bean = mock(ThreadMXBean.class);
-        when(bean.isThreadCpuTimeSupported()).thenReturn(false);
-        when(bean.isThreadContentionMonitoringSupported()).thenReturn(true);
-        
-        when(connection.createProxy(captor1.capture(), captor2.capture())).thenThrow(new MalformedObjectNameException()).thenReturn(bean);
-        
-        ScheduledExecutorService threadPool = mock(ScheduledExecutorService.class);
-        
-        /* ************* */
-        
-        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference, connector, threadPool);
-        VMThreadCapabilities caps = collector.getVMThreadCapabilities();
-
-        String beanName = captor1.getValue();
-        assertEquals(ManagementFactory.THREAD_MXBEAN_NAME, beanName);
-        
-        Class clazz = captor2.getValue();
-        assertEquals(ThreadMXBean.class.getName(), clazz.getName());
-        
-        verify(threadDao).loadCapabilities(reference);
-        
-        verify(connector).attach();
-        verify(connector).connect();
-        verify(connector).close();
-        
-        verify(threadDao).saveCapabilities(reference, caps);
-        
-        assertTrue(caps.supportContentionMonitor());
-        assertFalse(caps.supportCPUTime());
-        assertFalse(caps.supportThreadAllocatedMemory());
-    }
-    
-    @SuppressWarnings("restriction")
-    @Test
-    public void testHasThreadMemorySupport() throws Exception {
-        
-        VMThreadCapabilities referenceCaps = mock(VMThreadCapabilities.class);
-        when(referenceCaps.supportContentionMonitor()).thenReturn(true);
-        when(referenceCaps.supportCPUTime()).thenReturn(false);
-        
-        VmRef reference = mock(VmRef.class);
-        
-        ThreadDao threadDao = mock(ThreadDao.class);
-        when(threadDao.loadCapabilities(reference)).thenReturn(null);
-        
-        MXBeanConnector connector = mock(MXBeanConnector.class);
-        when(connector.isAttached()).thenReturn(false).thenReturn(true);
-        
-        MXBeanConnection connection = mock(MXBeanConnection.class);
-        when(connector.connect()).thenReturn(connection);
-                
-        ArgumentCaptor<String> captor1 = ArgumentCaptor.forClass(String.class);
-        ArgumentCaptor<Class> captor2 = ArgumentCaptor.forClass(Class.class);
-        
-        com.sun.management.ThreadMXBean bean = mock(com.sun.management.ThreadMXBean.class);
-        when(bean.isThreadCpuTimeSupported()).thenReturn(false);
-        when(bean.isThreadContentionMonitoringSupported()).thenReturn(true);
-        when(bean.isThreadAllocatedMemorySupported()).thenReturn(true);
-        
-        when(connection.createProxy(captor1.capture(), captor2.capture())).thenReturn(bean);
-        
-        ScheduledExecutorService threadPool = mock(ScheduledExecutorService.class);
-        
-        /* ************* */
-        
-        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference, connector, threadPool);
-        VMThreadCapabilities caps = collector.getVMThreadCapabilities();
-
-        String beanName = captor1.getValue();
-        assertEquals(ManagementFactory.THREAD_MXBEAN_NAME, beanName);
-        
-        Class clazz = captor2.getValue();
-        assertEquals(com.sun.management.ThreadMXBean.class.getName(), clazz.getName());
-        
-        verify(threadDao).loadCapabilities(reference);
-        
-        verify(connector).attach();
-        verify(connector).connect();
-        verify(connector).close();
-        
-        verify(threadDao).saveCapabilities(reference, caps);
-        
-        assertTrue(caps.supportContentionMonitor());
-        assertFalse(caps.supportCPUTime());
-        assertTrue(caps.supportThreadAllocatedMemory());
-    }
-    
-    @Test
-    public void testVMCapabilitiesInDAO() throws Exception {
-        
-        VMThreadCapabilities referenceCaps = mock(VMThreadCapabilities.class);
-        when(referenceCaps.supportContentionMonitor()).thenReturn(true);
-        when(referenceCaps.supportCPUTime()).thenReturn(false);
-        
-        VmRef reference = mock(VmRef.class);
-        
-        ThreadDao threadDao = mock(ThreadDao.class);
-        when(threadDao.loadCapabilities(reference)).thenReturn(referenceCaps);
-        
-        MXBeanConnector connector = mock(MXBeanConnector.class);
-        
-        ScheduledExecutorService threadPool = mock(ScheduledExecutorService.class);
-        
-        /* ************* */
-        
-        ThreadCollector collector = new ThreadMXBeanCollector(threadDao, reference, connector, threadPool);
-        VMThreadCapabilities caps = collector.getVMThreadCapabilities();
-
-        verify(threadDao).loadCapabilities(reference);
-        
-        verify(connector, times(0)).attach();
-        verify(connector, times(0)).connect();
-        verify(connector, times(0)).close();
-        
-        verify(threadDao, times(0)).saveCapabilities(reference, caps);
-        
-        assertTrue(caps.supportContentionMonitor());
-        assertFalse(caps.supportCPUTime());
-    }
-}
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -52,8 +52,8 @@
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Key;
 import com.redhat.thermostat.common.storage.Storage;
-import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
 import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
 public class ThreadDaoImplTest {
 
@@ -61,7 +61,9 @@
     public void testCreateConnectionKey() {
         Storage storage = mock(Storage.class);
         
+        @SuppressWarnings("unused")
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
+        
         verify(storage).createConnectionKey(ThreadDao.THREAD_CAPABILITIES);
         verify(storage).createConnectionKey(ThreadDao.THREAD_INFO);
         verify(storage).createConnectionKey(ThreadDao.THREAD_SUMMARY);
@@ -102,14 +104,6 @@
     public void testSaveVMCapabilities() {
         Storage storage = mock(Storage.class);
         
-        VmRef ref = mock(VmRef.class);
-        when(ref.getId()).thenReturn(42);
-        
-        HostRef agent = mock(HostRef.class);
-        when(agent.getAgentId()).thenReturn("0xcafe");
-
-        when(ref.getAgent()).thenReturn(agent);
-
         Chunk answer = mock(Chunk.class);
         when(answer.get(ThreadDao.CONTENTION_MONITOR_KEY)).thenReturn(false);
         when(answer.get(ThreadDao.CPU_TIME_KEY)).thenReturn(true);
@@ -120,7 +114,7 @@
         when(caps.supportThreadAllocatedMemory()).thenReturn(true);
         
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
-        dao.saveCapabilities(ref, caps);
+        dao.saveCapabilities("42", "0xcafe", caps);
 
         ArgumentCaptor<Chunk> queryCaptor = ArgumentCaptor.forClass(Chunk.class);
         verify(storage).putChunk(queryCaptor.capture());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/pom.xml	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,107 @@
+<?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-thread</artifactId>
+    <version>0.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-thread-harvester</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Thermostat Thread Info Harvester</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>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-thread-collector</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
+  </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.thread.harvester.osgi.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.thread.harvester</Bundle-SymbolicName>
+            <Private-Package>
+              com.redhat.thermostat.thread.harvester,
+              com.redhat.thermostat.thread.harvester.management,
+              com.redhat.thermostat.thread.harvester.osgi,
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/main/java/com/redhat/thermostat/thread/harvester/Harvester.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,287 @@
+/*
+ * 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.thread.harvester;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.management.MalformedObjectNameException;
+
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.harvester.management.MXBeanConnection;
+import com.redhat.thermostat.thread.harvester.management.MXBeanConnector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+
+@SuppressWarnings("restriction")
+class Harvester {
+    
+    private static final Logger logger = Logger.getLogger(Harvester.class.getSimpleName());
+    
+    private boolean isConnected;
+    private ScheduledExecutorService threadPool;
+    private ScheduledFuture<?> harvester;
+    
+    MXBeanConnector connector;
+    
+    private MXBeanConnection connection;
+    private ThreadMXBean collectorBean;
+    private ThreadDao threadDao;
+    private String vmId;
+    private String agentId;
+    
+    Harvester(ThreadDao threadDao, ScheduledExecutorService threadPool, String vmId, String agentId) {
+        this.connector = new MXBeanConnector(vmId);
+        this.threadDao = threadDao;
+        this.vmId = vmId;
+        this.agentId = agentId;
+        this.threadPool = threadPool;
+    }
+    
+    synchronized boolean start() {
+        if (isConnected) {
+            return true;
+        }
+
+        if (!connector.isAttached()) {
+            try {
+                connector.attach();
+            } catch (Exception ex) {
+                logger.log(Level.SEVERE, "can't attach", ex);
+                return false;
+            }
+        }
+      
+        try {
+            connection = connector.connect();
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "can't connect", ex);
+            return false;
+        }
+        
+        isConnected = true;
+        harvester = threadPool.scheduleAtFixedRate(new HarvesterAction(), 0, 1, TimeUnit.SECONDS);
+        
+        return isConnected;
+    }
+    
+    boolean isConnected() {
+        return isConnected;
+    }
+    
+    synchronized boolean stop() {
+        if (!isConnected) {
+            return true;
+        }
+        
+        harvester.cancel(false);
+        isConnected = false;
+
+        boolean stillConnected = false;
+        try {
+            connection.close();
+        } catch (IOException ex) {
+            logger.log(Level.SEVERE, "can't close connection", ex);
+            stillConnected = true;
+        }
+
+        if (connector.isAttached()) {
+            try {
+                connector.close();
+            } catch (Exception ex) {
+                logger.log(Level.SEVERE, "can't detach", ex);
+                if (stillConnected) {
+                    isConnected = true;
+                }
+                return false;
+            }
+        }
+
+        return true;
+    }
+    
+    ThreadMXBean getDataCollectorBean(MXBeanConnection connection)
+            throws MalformedObjectNameException
+    {
+        ThreadMXBean bean = null;
+        try {
+            bean = connection.createProxy(ManagementFactory.THREAD_MXBEAN_NAME,
+                                          com.sun.management.ThreadMXBean.class);
+        } catch (MalformedObjectNameException ignore) {}
+
+        if (bean == null) {
+            bean = connection.createProxy(ManagementFactory.THREAD_MXBEAN_NAME,
+                                          ThreadMXBean.class);
+        }
+        return bean;
+    }
+    
+    synchronized void harvestData() {
+      try {
+          long timestamp = System.currentTimeMillis();
+          
+          ThreadSummary summary = new ThreadSummary();
+          
+          collectorBean = getDataCollectorBean(connection);
+          
+          summary.setCurrentLiveThreads(collectorBean.getThreadCount());
+          summary.setDaemonThreads(collectorBean.getDaemonThreadCount());
+          summary.setTimestamp(timestamp);
+          
+          threadDao.saveSummary(vmId, agentId, summary);
+          
+          long [] ids = collectorBean.getAllThreadIds();
+          long[] allocatedBytes = null;
+          
+          // now the details for the threads
+          if (collectorBean instanceof com.sun.management.ThreadMXBean) {
+              com.sun.management.ThreadMXBean sunBean = (com.sun.management.ThreadMXBean) collectorBean;
+              boolean wasEnabled = false;
+              try {
+                  if (sunBean.isThreadAllocatedMemorySupported()) {
+                      wasEnabled = sunBean.isThreadAllocatedMemoryEnabled();
+                      sunBean.setThreadAllocatedMemoryEnabled(true);
+                      allocatedBytes = sunBean.getThreadAllocatedBytes(ids);
+                      sunBean.setThreadAllocatedMemoryEnabled(wasEnabled);
+                  }
+              } catch (Exception ignore) {}
+          }
+
+          ThreadInfo[] threadInfos = collectorBean.getThreadInfo(ids, true, true);
+          
+          for (int i = 0; i < ids.length; i++) {
+              ThreadInfoData info = new ThreadInfoData();
+              ThreadInfo beanInfo = threadInfos[i];
+
+              info.setTimeStamp(timestamp);
+
+              info.setName(beanInfo.getThreadName());
+              info.setID(beanInfo.getThreadId());
+              info.setState(beanInfo.getThreadState());
+              info.setStackTrace(beanInfo.getStackTrace());
+
+              info.setCPUTime(collectorBean.getThreadCpuTime(info.getThreadID()));
+              info.setUserTime(collectorBean.getThreadUserTime(info.getThreadID()));
+              
+              info.setBlockedCount(beanInfo.getBlockedCount());
+              info.setWaitedCount(beanInfo.getWaitedCount());
+              
+              if (allocatedBytes != null) {
+                  info.setAllocatedBytes(allocatedBytes[i]);
+              }
+
+              threadDao.saveThreadInfo(vmId, agentId, info);
+          }
+          
+      } catch (MalformedObjectNameException e) {
+          e.printStackTrace();
+      }
+    }
+    
+    private class HarvesterAction implements Runnable {
+        @Override
+        public void run() {
+            harvestData();
+        }
+    }
+    
+    synchronized boolean saveVmCaps() {
+        
+        boolean closeAfter = false;
+        if (!connector.isAttached()) {
+            closeAfter = true; 
+            try {
+                connector.attach();
+            } catch (Exception ex) {
+                logger.log(Level.SEVERE, "can't attach", ex);
+                if (closeAfter) {
+                    closeConnection();
+                }
+                return false;
+            }
+        }
+
+        try (MXBeanConnection connection = connector.connect()) {
+
+            ThreadMXBean bean = getDataCollectorBean(connection);
+            VMThreadCapabilities caps = new VMThreadCapabilities();
+
+            if (bean.isThreadCpuTimeSupported())
+                caps.addFeature(ThreadDao.CPU_TIME);
+            if (bean.isThreadContentionMonitoringSupported())
+                caps.addFeature(ThreadDao.CONTENTION_MONITOR);
+
+            if (bean instanceof com.sun.management.ThreadMXBean) {
+                com.sun.management.ThreadMXBean sunBean = (com.sun.management.ThreadMXBean) bean;
+                
+                try {
+                    if (sunBean.isThreadAllocatedMemorySupported()) {
+                        caps.addFeature(ThreadDao.THREAD_ALLOCATED_MEMORY);
+                    }
+                } catch (Exception ignore) {};
+            }
+
+            threadDao.saveCapabilities(vmId, agentId, caps);
+
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "can't get MXBeanConnection connection", ex);
+            return false;
+        }
+
+        if (closeAfter) {
+            closeConnection();
+        }
+        
+        return true;
+    }
+    
+    private void closeConnection() {
+        try {
+            connector.close();
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "can't close connection to vm", ex);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/main/java/com/redhat/thermostat/thread/harvester/ThreadHarvester.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,142 @@
+/*
+ * 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.thread.harvester;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+
+import com.redhat.thermostat.agent.command.RequestReceiver;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+
+import com.redhat.thermostat.thread.collector.HarvesterCommand;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+
+public class ThreadHarvester implements RequestReceiver {
+
+    private ScheduledExecutorService executor;
+    Map<String, Harvester> connectors;
+
+    private ThreadDao dao;
+    
+    public ThreadHarvester(ScheduledExecutorService executor, ThreadDao dao) {
+        this.executor = executor;
+        connectors = new HashMap<>();
+        this.dao = dao;
+    }
+    
+    @Override
+    public Response receive(Request request) {
+        
+        boolean result = false;
+        
+        String command = request.getParameter(HarvesterCommand.class.getName());
+        switch (HarvesterCommand.valueOf(command)) {
+        case START: {
+            String vmId = request.getParameter(HarvesterCommand.VM_ID.name());
+            String agentId = request.getParameter(HarvesterCommand.AGENT_ID.name());
+            result = startHarvester(vmId, agentId);
+            break;
+        }   
+        case STOP: {
+            String vmId = request.getParameter(HarvesterCommand.VM_ID.name());
+            result = stopHarvester(vmId);
+            break;
+        }
+        case VM_CAPS: {
+            // this is blocking
+            String vmId = request.getParameter(HarvesterCommand.VM_ID.name());
+            String agentId = request.getParameter(HarvesterCommand.AGENT_ID.name());
+            result = saveVmCaps(vmId, agentId);
+            break;
+        }
+        case IS_COLLECTING: {
+            // this is blocking too
+            String vmId = request.getParameter(HarvesterCommand.VM_ID.name());
+            // FIXME: this need to be replaced when we support response parameters
+            return isCollecting(vmId) ? new Response(ResponseType.OK) : new Response(ResponseType.NOK);
+        }        
+        default:
+            result = false;
+            break;
+        }
+        
+        if (result) {
+            return new Response(ResponseType.OK);            
+        } else {
+            return new Response(ResponseType.ERROR);
+        }
+    }
+    
+    private boolean isCollecting(String vmId) {
+        Harvester harvester = connectors.get(vmId);
+        if (harvester == null) {
+            return false;
+        }
+        return harvester.isConnected();
+    }
+    
+    private boolean startHarvester(String vmId, String agentId) {
+        Harvester harvester = getHarvester(vmId, agentId);
+        return harvester.start();
+    }
+    
+    private boolean saveVmCaps(String vmId, String agentId) {
+        Harvester harvester = getHarvester(vmId, agentId);
+        return harvester.saveVmCaps();
+    }
+    
+    private boolean stopHarvester(String vmId) {
+        Harvester harvester = connectors.get(vmId);
+        if (harvester != null) {
+            return harvester.stop();
+        }
+        return true;
+    }
+    
+    Harvester getHarvester(String vmId, String agentId) {
+        Harvester harvester = connectors.get(vmId);
+        if (harvester == null) {
+            harvester = new Harvester(dao, executor, vmId, agentId);
+            connectors.put(vmId, harvester);
+        }
+        
+        return harvester;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/main/java/com/redhat/thermostat/thread/harvester/management/MXBeanConnection.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.harvester.management;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import javax.management.JMX;
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+
+public class MXBeanConnection implements Closeable {
+
+    private JMXConnector connection;
+    private MBeanServerConnection mbsc;
+    
+    MXBeanConnection(JMXConnector connection, MBeanServerConnection mbsc) {
+        this.connection = connection;
+        this.mbsc = mbsc;
+    }
+    
+    public synchronized <E> E createProxy(String name, Class<? extends E> proxyClass) throws MalformedObjectNameException {
+        ObjectName objectName = new ObjectName(name);
+        return JMX.newMXBeanProxy(mbsc, objectName, proxyClass);
+    }
+    
+    @Override
+    public void close() throws IOException {
+        connection.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/main/java/com/redhat/thermostat/thread/harvester/management/MXBeanConnector.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.harvester.management;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.management.MBeanServerConnection;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import com.redhat.thermostat.common.dao.VmRef;
+import com.sun.tools.attach.VirtualMachine;
+
+public class MXBeanConnector implements Closeable {
+
+    private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress";
+    private String connectorAddress;
+    
+    private VirtualMachine vm;
+    
+    private boolean attached;
+    
+    private String reference;
+    
+    public MXBeanConnector(String reference) {
+        this.reference = reference;
+    }
+    
+    public MXBeanConnector(VmRef reference) {
+        this.reference = reference.getStringID();
+    }
+    
+    public synchronized void attach() throws Exception {
+        if (attached)
+            throw new IOException("Already attached");
+        
+        vm = VirtualMachine.attach(reference);
+        attached = true;
+        
+        Properties props = vm.getAgentProperties();
+        connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
+        if (connectorAddress == null) {
+           props = vm.getSystemProperties();
+           String home = props.getProperty("java.home");
+           String agent = home + File.separator + "lib" + File.separator + "management-agent.jar";
+           vm.loadAgent(agent);
+           
+           props = vm.getAgentProperties();
+           connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY);
+        }
+    }
+    
+    public synchronized MXBeanConnection connect() throws Exception {
+        
+        if (!attached)
+            throw new IOException("Agent not attached to target VM");
+        
+        JMXServiceURL url = new JMXServiceURL(connectorAddress);
+        JMXConnector connection = JMXConnectorFactory.connect(url);
+        MBeanServerConnection mbsc = null;
+        try {
+            mbsc = connection.getMBeanServerConnection();
+            
+        } catch (IOException e) {
+            connection.close();
+            throw e;
+        }
+        
+        return new MXBeanConnection(connection, mbsc);
+    }
+    
+    public boolean isAttached() {
+        return attached;
+    }
+    
+    @Override
+    public synchronized void close() throws IOException {
+        if (attached) {
+            vm.detach();
+            attached = false;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/main/java/com/redhat/thermostat/thread/harvester/osgi/Activator.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.harvester.osgi;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.agent.command.ReceiverRegistry;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.harvester.ThreadHarvester;
+
+public class Activator implements BundleActivator {
+    
+    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(24);
+    
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        ServiceTracker tracker = new ServiceTracker(context, ThreadDao.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                ThreadDao threadDao = (ThreadDao) context.getService(reference);
+
+                ThreadHarvester harvester = new ThreadHarvester(executor, threadDao);
+  
+                ReceiverRegistry registry = new ReceiverRegistry(context);
+                registry.registerReceiver(harvester);
+                
+                return super.addingService(reference);
+            }
+        };
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        if (executor != null) {
+            executor.shutdown();
+        }        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/test/java/com/redhat/thermostat/thread/harvester/HarvesterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -0,0 +1,400 @@
+/*
+ * 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.thread.harvester;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+
+import static org.junit.Assert.*;
+
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.MalformedObjectNameException;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.thread.dao.ThreadDao;
+import com.redhat.thermostat.thread.harvester.management.MXBeanConnection;
+import com.redhat.thermostat.thread.harvester.management.MXBeanConnector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+import com.redhat.thermostat.thread.model.ThreadSummary;
+import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+
+public class HarvesterTest {
+
+    @Test
+    public void testStart() {
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        when(mockedConnector.isAttached()).thenReturn(false);
+        
+        ArgumentCaptor<Runnable> arg0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Long> arg1 = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Long> arg2 = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<TimeUnit> arg3 = ArgumentCaptor.forClass(TimeUnit.class);
+        
+        final boolean [] harvestDataCalled = new boolean[1];
+        
+        when(executor.scheduleAtFixedRate(arg0.capture(), arg1.capture(), arg2.capture(), arg3.capture())).thenReturn(null);
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe") {
+            { connector = mockedConnector; }
+            @Override
+            synchronized void harvestData() {
+                harvestDataCalled[0] = true;
+            }
+        };
+        
+        harvester.start();
+        
+        verify(executor).scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
+        
+        assertTrue(arg1.getValue() == 0);
+        assertTrue(arg2.getValue() == 1);
+        assertEquals(TimeUnit.SECONDS, arg3.getValue());
+        
+        Runnable action = arg0.getValue();
+        assertNotNull(action);
+        
+        action.run();
+        
+        assertTrue(harvestDataCalled[0]);
+        
+        assertTrue(harvester.isConnected());
+    }
+    
+    /**
+     *  Mostly the same as testStart, but we call harvester.start() twice
+     */
+    @Test
+    public void testStartOnce() throws Exception {
+
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        when(mockedConnector.isAttached()).thenReturn(false);
+        
+        ArgumentCaptor<Runnable> arg0 = ArgumentCaptor.forClass(Runnable.class);
+        ArgumentCaptor<Long> arg1 = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<Long> arg2 = ArgumentCaptor.forClass(Long.class);
+        ArgumentCaptor<TimeUnit> arg3 = ArgumentCaptor.forClass(TimeUnit.class);
+        
+        final boolean [] harvestDataCalled = new boolean[1];
+        
+        when(executor.scheduleAtFixedRate(arg0.capture(), arg1.capture(), arg2.capture(), arg3.capture())).thenReturn(null);
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe") {
+            { connector = mockedConnector; }
+            @Override
+            synchronized void harvestData() {
+                harvestDataCalled[0] = true;
+            }
+        };
+        
+        harvester.start();
+        harvester.start();
+
+        verify(mockedConnector, times(1)).isAttached();
+        verify(mockedConnector, times(1)).attach();
+        verify(executor, times(1)).scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
+        
+        assertTrue(arg1.getValue() == 0);
+        assertTrue(arg2.getValue() == 1);
+        assertEquals(TimeUnit.SECONDS, arg3.getValue());
+        
+        Runnable action = arg0.getValue();
+        assertNotNull(action);
+        
+        action.run();
+        
+        assertTrue(harvestDataCalled[0]);
+        
+        assertTrue(harvester.isConnected());
+    }
+    
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testStopAfterStarting() throws Exception {
+        
+        ScheduledFuture future = mock(ScheduledFuture.class);
+        
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        
+        MXBeanConnection connection = mock(MXBeanConnection.class);
+        
+        when(mockedConnector.connect()).thenReturn(connection);
+        
+        when(mockedConnector.isAttached()).thenReturn(true);
+        
+        when(executor.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(future);
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe")
+        {{ connector = mockedConnector; }};
+        
+        harvester.start();
+        
+        assertTrue(harvester.isConnected());
+        
+        harvester.stop();
+        
+        verify(future).cancel(false);
+        verify(connection).close();
+        
+        // needs to be 2 times, since is called once in start
+        verify(mockedConnector, times(2)).isAttached();
+        verify(mockedConnector).close();
+        
+        assertFalse(harvester.isConnected());
+    }
+    
+    /**
+     *  Mostly the same as testStopAfterStarting, but we call harvester.stop()
+     *  twice
+     */
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testStopTwiceAfterStarting() throws Exception {
+        
+        ScheduledFuture future = mock(ScheduledFuture.class);
+        
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        
+        MXBeanConnection connection = mock(MXBeanConnection.class);
+        
+        when(mockedConnector.connect()).thenReturn(connection);
+        
+        when(mockedConnector.isAttached()).thenReturn(true);
+        
+        when(executor.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(future);
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe")
+        {{ connector = mockedConnector; }};
+        
+        harvester.start();
+        
+        assertTrue(harvester.isConnected());
+        
+        harvester.stop();
+        harvester.stop();
+
+        verify(future, times(1)).cancel(false);
+        verify(connection, times(1)).close();
+        
+        // needs to be 2 times, since is called once in start
+        verify(mockedConnector, times(2)).isAttached();
+        
+        verify(mockedConnector, times(1)).close();
+        
+        assertFalse(harvester.isConnected());
+    }
+    
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testStopNotStarted() throws Exception {
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        
+        ScheduledFuture future = mock(ScheduledFuture.class);
+        when(executor.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class))).thenReturn(future);
+        
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        MXBeanConnection connection = mock(MXBeanConnection.class);
+        when(mockedConnector.connect()).thenReturn(connection);
+        when(mockedConnector.isAttached()).thenReturn(true);
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe")
+        {{ connector = mockedConnector; }};
+        
+        verify(executor, times(0)).scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(TimeUnit.class));
+        
+        assertFalse(harvester.isConnected());
+        
+        harvester.stop();
+
+        assertFalse(harvester.isConnected());
+        verify(mockedConnector, times(0)).isAttached();
+        
+        verify(future, times(0)).cancel(false);
+    }
+    
+    @Test
+    public void testHarvestData() {
+        
+        long ids[] = new long [] {
+            0, 1
+        };
+        
+        ThreadInfo info1 = mock(ThreadInfo.class);
+        when(info1.getThreadName()).thenReturn("fluff1");
+        when(info1.getThreadId()).thenReturn(1l);
+
+        ThreadInfo info2 = mock(ThreadInfo.class);
+        when(info2.getThreadName()).thenReturn("fluff2");
+        when(info2.getThreadId()).thenReturn(2l);
+
+        ThreadInfo[] infos = new ThreadInfo[] {
+            info1,
+            info2     
+        };
+                
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+
+        ArgumentCaptor<String> vmCapture = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> agentCapture = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<ThreadSummary> summaryCapture = ArgumentCaptor.forClass(ThreadSummary.class);
+
+        ThreadDao dao = mock(ThreadDao.class);
+        doNothing().when(dao).saveSummary(vmCapture.capture(), agentCapture.capture(), summaryCapture.capture());
+        
+        ArgumentCaptor<String> vmCapture2 = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> agentCapture2 = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<ThreadInfoData> threadInfoCapture = ArgumentCaptor.forClass(ThreadInfoData.class);        
+        doNothing().when(dao).saveThreadInfo(vmCapture2.capture(), agentCapture2.capture(), threadInfoCapture.capture());
+
+        final ThreadMXBean collectorBean = mock(ThreadMXBean.class);
+
+        when(collectorBean.getThreadCount()).thenReturn(42);
+        when(collectorBean.getAllThreadIds()).thenReturn(ids);
+        when(collectorBean.getThreadInfo(ids, true, true)).thenReturn(infos);
+
+        final boolean [] getDataCollectorBeanCalled = new boolean[1];
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe") {
+            @Override
+            ThreadMXBean getDataCollectorBean(MXBeanConnection connection)
+                    throws MalformedObjectNameException {
+                getDataCollectorBeanCalled[0] = true;
+                return collectorBean;
+            }
+        };
+
+        harvester.harvestData();
+        
+        assertTrue(getDataCollectorBeanCalled[0]);
+        
+        verify(collectorBean).getThreadInfo(ids, true, true);
+        
+        verify(dao).saveSummary(anyString(), anyString(), any(ThreadSummary.class));
+        
+        // once for each thread info
+        verify(dao, times(2)).saveThreadInfo(anyString(), anyString(), any(ThreadInfoData.class));
+        
+        assertEquals(42, summaryCapture.getValue().currentLiveThreads());
+        assertEquals("42", vmCapture.getValue());
+        assertEquals("0xcafe", agentCapture.getValue());
+        
+        assertEquals(42, summaryCapture.getValue().currentLiveThreads());
+        assertEquals("42", vmCapture2.getAllValues().get(0));
+        assertEquals("42", vmCapture2.getAllValues().get(1));
+
+        assertEquals("0xcafe", agentCapture2.getAllValues().get(0));
+        assertEquals("0xcafe", agentCapture2.getAllValues().get(1));
+        
+        List<ThreadInfoData> threadInfos = threadInfoCapture.getAllValues();
+        assertEquals(2, threadInfos.size());
+        
+        assertEquals("fluff1", threadInfos.get(0).getName());
+        assertEquals("fluff2", threadInfos.get(1).getName());
+        
+        verify(collectorBean, times(1)).getThreadCpuTime(1l);
+        verify(collectorBean, times(1)).getThreadCpuTime(2l);
+    }
+    
+    @Test
+    public void testSaveVmCaps() {
+
+        final MXBeanConnector mockedConnector = mock(MXBeanConnector.class);
+        when(mockedConnector.isAttached()).thenReturn(true);
+        
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+
+        ArgumentCaptor<String> vmCapture = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> agentCapture = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<VMThreadCapabilities> capsCapture = ArgumentCaptor.forClass(VMThreadCapabilities.class);
+
+        ThreadDao dao = mock(ThreadDao.class);
+        doNothing().when(dao).saveCapabilities(vmCapture.capture(), agentCapture.capture(), capsCapture.capture());
+      
+        final ThreadMXBean collectorBean = mock(ThreadMXBean.class);
+        when(collectorBean.isThreadCpuTimeSupported()).thenReturn(true);
+        when(collectorBean.isThreadContentionMonitoringSupported()).thenReturn(true);
+
+        final boolean [] getDataCollectorBeanCalled = new boolean[1];
+        
+        Harvester harvester = new Harvester(dao, executor, "42", "0xcafe") {
+            { connector = mockedConnector; }
+            @Override
+            ThreadMXBean getDataCollectorBean(MXBeanConnection connection)
+                    throws MalformedObjectNameException {
+                getDataCollectorBeanCalled[0] = true;
+                return collectorBean;
+            }
+        };
+
+        harvester.saveVmCaps();
+        assertTrue(getDataCollectorBeanCalled[0]);
+        
+        verify(dao, times(1)).saveCapabilities(anyString(), anyString(), any(VMThreadCapabilities.class));
+        assertEquals("42", vmCapture.getValue());
+        assertEquals("0xcafe", agentCapture.getValue());
+
+        List<String> features = capsCapture.getValue().getSupportedFeaturesList();
+        assertEquals(2, features.size());
+        assertTrue(features.contains(ThreadDao.CPU_TIME));
+        assertTrue(features.contains(ThreadDao.CONTENTION_MONITOR));
+        assertFalse(features.contains(ThreadDao.THREAD_ALLOCATED_MEMORY));
+    }    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/harvester/src/test/java/com/redhat/thermostat/thread/harvester/ThreadHarvesterTest.java	Fri Aug 31 11:26:14 2012 +0200
@@ -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.thread.harvester;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.thread.collector.HarvesterCommand;
+import com.redhat.thermostat.thread.dao.ThreadDao;
+
+public class ThreadHarvesterTest {
+
+    @Test
+    public void testStart() {
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        Request request = mock(Request.class);
+        
+        final boolean[] getHarvesterCalled = new boolean[1];
+        final Harvester harverster = mock(Harvester.class);
+        
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        
+        when(request.getParameter(captor.capture())).
+            thenReturn(HarvesterCommand.START.name()).
+            thenReturn("42").
+            thenReturn("0xcafe");
+        
+        ThreadHarvester threadHarvester = new ThreadHarvester(executor, dao) {
+            @Override
+            Harvester getHarvester(String vmId, String agentId) {
+                
+                getHarvesterCalled[0] = true;
+                assertEquals("42", vmId);
+                assertEquals("0xcafe", agentId);
+                
+                return harverster;
+            }
+        };
+        threadHarvester.receive(request);
+        
+        List<String> values = captor.getAllValues();
+        assertEquals(3, values.size());
+        
+        assertEquals(HarvesterCommand.class.getName(), values.get(0));
+        assertEquals(HarvesterCommand.VM_ID.name(), values.get(1));
+        assertEquals(HarvesterCommand.AGENT_ID.name(), values.get(2));
+        
+        assertTrue(getHarvesterCalled[0]);
+        
+        verify(harverster).start();
+    }
+
+    @Test
+    public void testStop() {
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        Request request = mock(Request.class);
+        
+        final Harvester harverster = mock(Harvester.class);
+        
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        
+        when(request.getParameter(captor.capture())).
+            thenReturn(HarvesterCommand.STOP.name()).
+            thenReturn("42");
+        
+        ThreadHarvester threadHarvester = new ThreadHarvester(executor, dao) {
+            { connectors.put("42", harverster); }
+        };
+        threadHarvester.receive(request);
+        
+        List<String> values = captor.getAllValues();
+        assertEquals(2, values.size());
+        
+        assertEquals(HarvesterCommand.class.getName(), values.get(0));
+        assertEquals(HarvesterCommand.VM_ID.name(), values.get(1));
+                
+        verify(harverster).stop();        
+    }
+    
+    @Test
+    public void testSaveVmCaps() {
+        ScheduledExecutorService executor = mock(ScheduledExecutorService.class);
+        ThreadDao dao = mock(ThreadDao.class);
+        Request request = mock(Request.class);
+        
+        final boolean[] getHarvesterCalled = new boolean[1];
+        final Harvester harverster = mock(Harvester.class);
+        
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        
+        when(request.getParameter(captor.capture())).
+            thenReturn(HarvesterCommand.VM_CAPS.name()).
+            thenReturn("42").
+            thenReturn("0xcafe");
+        
+        ThreadHarvester threadHarvester = new ThreadHarvester(executor, dao) {
+            @Override
+            Harvester getHarvester(String vmId, String agentId) {
+                
+                getHarvesterCalled[0] = true;
+                assertEquals("42", vmId);
+                assertEquals("0xcafe", agentId);
+                
+                return harverster;
+            }
+        };
+        threadHarvester.receive(request);
+        
+        List<String> values = captor.getAllValues();
+        assertEquals(3, values.size());
+        
+        assertEquals(HarvesterCommand.class.getName(), values.get(0));
+        assertEquals(HarvesterCommand.VM_ID.name(), values.get(1));
+        assertEquals(HarvesterCommand.AGENT_ID.name(), values.get(2));
+        
+        assertTrue(getHarvesterCalled[0]);
+        
+        verify(harverster).saveVmCaps();
+    }    
+}
--- a/thread/pom.xml	Fri Aug 31 11:22:52 2012 +0200
+++ b/thread/pom.xml	Fri Aug 31 11:26:14 2012 +0200
@@ -60,10 +60,10 @@
 
   <modules>
     <module>collector</module>
+    <module>harvester</module>
     <module>client-common</module>
     <module>client-swing</module>
     <module>client-controllers</module>
   </modules>
-
 </project>