changeset 1383:21466861b0ef

Add thermostat multi-module plug-in archetype. Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-January/009130.html Reviewed-by: neugens PR1671
author Severin Gehwolf <sgehwolf@redhat.com>
date Tue, 21 Jan 2014 19:17:27 +0100
parents 7caa7f9afb14
children 87db81dbf869
files dev/multi-module-plugin-archetype/pom.xml dev/multi-module-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/README dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/pom.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/src/main/java/agent/internal/Activator.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/src/main/java/agent/internal/ExampleBackend.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/pom.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/src/main/java/cli/internal/Activator.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/src/main/java/cli/internal/ExampleCommand.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/deploy.sh dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/distribution/pom.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/distribution/thermostat-plugin.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/pom.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/pom.xml dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/ExampleDAO.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/ExampleMessage.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/internal/Activator.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/internal/ExampleDAOImpl.java dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/test/java/storage/ExampleMessageTest.java dev/multi-module-plugin-archetype/src/test/resources/projects/basic/archetype.properties dev/pom.xml
diffstat 21 files changed, 1362 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>com.redhat.thermostat</groupId>
+  <artifactId>thermostat-maven-archetype-multimodule</artifactId>
+  <version>1.1.0-SNAPSHOT</version>
+  <packaging>maven-archetype</packaging>
+
+  <name>Thermostat archetype for a multi-module plugin scaffold</name>
+  
+  <build>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.archetype</groupId>
+        <artifactId>archetype-packaging</artifactId>
+        <version>2.2</version>
+      </extension>
+    </extensions>
+
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <artifactId>maven-archetype-plugin</artifactId>
+          <version>2.2</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+
+  <url>http://icedtea.classpath.org/thermostat/</url>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archetype-descriptor xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0 http://maven.apache.org/xsd/archetype-descriptor-1.0.0.xsd" name="thermostat-kernel-cmdline"
+    xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype-descriptor/1.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <requiredProperties>
+    <requiredProperty key="thermostat-core-version">
+      <defaultValue>1.1.0-SNAPSHOT</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="pluginDescription">
+      <defaultValue>Thermostat example plugin</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="helloMessage">
+      <defaultValue>Hello World!</defaultValue>
+    </requiredProperty>
+    <requiredProperty key="pluginDeployDir">
+      <defaultValue>example-plugin</defaultValue>
+    </requiredProperty>
+  </requiredProperties>
+  <fileSets>
+    <fileSet filtered="true" encoding="UTF-8">
+      <directory></directory>
+      <includes>
+        <include>deploy.sh</include>
+        <include>README</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+  <modules>
+    <module id="${rootArtifactId}-cli" dir="client-cli" name="${rootArtifactId}-cli">
+      <fileSets>
+        <fileSet filtered="true" packaged="true" encoding="UTF-8">
+          <directory>src/main/java</directory>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+        </fileSet>
+      </fileSets>
+    </module>
+    <module id="${rootArtifactId}-agent" dir="agent" name="${rootArtifactId}-agent">
+      <fileSets>
+        <fileSet filtered="true" packaged="true" encoding="UTF-8">
+          <directory>src/main/java</directory>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+        </fileSet>
+        <fileSet filtered="true" packaged="true" encoding="UTF-8">
+          <directory>src/test/java</directory>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+        </fileSet>
+        <fileSet filtered="true" encoding="UTF-8">
+          <directory>src/test/resources</directory>
+          <includes>
+            <include>**/*.txt</include>
+          </includes>
+        </fileSet>
+      </fileSets>
+    </module>
+    <module id="${rootArtifactId}-storage-common" dir="storage-common" name="${rootArtifactId}-storage-common">
+      <fileSets>
+        <fileSet filtered="true" packaged="true" encoding="UTF-8">
+          <directory>src/main/java</directory>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+        </fileSet>
+        <fileSet filtered="true" packaged="true" encoding="UTF-8">
+          <directory>src/test/java</directory>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+        </fileSet>
+      </fileSets>
+    </module>
+    <module id="${rootArtifactId}-distribution" dir="distribution" name="${rootArtifactId}-distribution">
+      <fileSets>
+        <fileSet filtered="true" encoding="UTF-8">
+          <directory></directory>
+          <includes>
+            <include>thermostat-plugin.xml</include>
+          </includes>
+        </fileSet>
+      </fileSets>
+    </module>
+  </modules>
+</archetype-descriptor>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/README	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,28 @@
+This is a simple multi-module Thermostat[1] plugin scaffold.
+
+The structure in the file system looks as follows:
+
+└── foo-plugin
+    ├── pom.xml (parent pom)
+    ├── agent
+    ├── storage-common
+    ├── client-cli
+    └── distribution
+
+The agent directory contains plug-in bits for thermostat agents. This usually
+includes some form of back-end.
+
+The client-cli directory contains plug-in bits for thermostat command-line
+clients.
+
+The storage-common directory contains model and DAO classes which your plug-in
+may contribute. This module is usually a dependency of the agent and client-cli
+modules.
+
+The distribution directory is an assembly helper for your plug-in. The most
+important part in there is thermostat-plugin.xml. The assembled plug-in can
+be found in this module's target directory after a successful maven build.
+
+Enjoy!
+
+[1] http://icedtea.classpath.org/thermostat/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012, 2013 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>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <artifactId>${artifactId}</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>${pluginDescription} - Agent</name>
+
+  <dependencies>
+        
+      <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>org.osgi.core</artifactId>
+          <scope>provided</scope>
+      </dependency>
+
+      <!-- thermostat specific dependencies -->
+    
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-agent-core</artifactId>
+      </dependency>
+    
+      <!-- plugin specific dependencies -->
+      
+      <dependency>
+          <groupId>${groupId}</groupId>
+          <artifactId>${rootArtifactId}-storage-common</artifactId>
+    	  <version>${version}</version>
+      </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>${package}.agent.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>${package}.agent</Bundle-SymbolicName>
+            <Private-Package>${package}.agent.internal</Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+      
+      <!-- No tests in this module, skip them in order to make the build pass -->    
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>true</skipTests>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/src/main/java/agent/internal/Activator.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,59 @@
+package ${package}.agent.internal;
+
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.backend.Backend;
+import com.redhat.thermostat.backend.BackendService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.Version;
+import com.redhat.thermostat.storage.core.WriterID;
+import ${package}.storage.ExampleDAO;
+
+public class Activator implements BundleActivator {
+
+    private MultipleServiceTracker tracker;
+    private ServiceRegistration reg;
+    private Backend backend;
+    
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        Class<?>[] deps = new Class<?>[] {
+                BackendService.class, 
+                ExampleDAO.class, 
+                ApplicationService.class,
+                WriterID.class
+        };
+        tracker = new MultipleServiceTracker(context, deps, new Action() {
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                ApplicationService appService = (ApplicationService) services.get(ApplicationService.class.getName());
+                ExampleDAO dao = (ExampleDAO) services.get(ExampleDAO.class.getName());
+                WriterID writer = (WriterID) services.get(WriterID.class.getName());
+                Version version = new Version(context.getBundle());
+                backend = new ExampleBackend(appService, version, dao, writer);
+                reg = context.registerService(Backend.class.getName(), backend, null);
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                if (backend.isActive()) {
+                    backend.deactivate();
+                }
+                reg.unregister();
+            }
+        });
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/agent/src/main/java/agent/internal/ExampleBackend.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,75 @@
+package ${package}.agent.internal;
+
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.backend.BaseBackend;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Ordered;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.Version;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.WriterID;
+import ${package}.storage.ExampleDAO;
+
+public class ExampleBackend extends BaseBackend {
+
+    private static final int CHECK_INTERVAL_MINUTES = 60;
+    
+    private final ApplicationService appService;
+    private Timer timer;
+    private final ExampleDAO dao;
+    private boolean started;
+    
+    public ExampleBackend(ApplicationService service, Version version, ExampleDAO dao, WriterID writer) {
+        super("Example Backend",
+                "Saves a message for an agent",
+                "Red Hat, Inc.",
+                version.getVersionNumber());
+        this.appService = service;
+        this.dao = dao;
+        this.started = false;
+    }
+    
+    @Override
+    public boolean activate() {
+        // This is silly and shouldn't really do this every 60 minutes.
+        // Anyhow, it's good to illustrate thermostat timers using appService.
+        TimerFactory timerFactory = appService.getTimerFactory();
+        timer = timerFactory.createTimer();
+        timer.setDelay(CHECK_INTERVAL_MINUTES);
+        timer.setInitialDelay(0);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+        timer.setTimeUnit(TimeUnit.MINUTES);
+        timer.setAction(new Runnable() {
+
+            @Override
+            public void run() {
+                dao.putMessage("${helloMessage}");
+            }
+        });
+        timer.start();
+        started = true;
+        return true;
+    }
+
+    @Override
+    public boolean deactivate() {
+        started = false;
+        timer.stop();
+        return true;
+    }
+
+    @Override
+    public boolean isActive() {
+        return started;
+    }
+
+    @Override
+    public int getOrderValue() {
+        // offset should be < 100 in this case 88.
+        return Ordered.ORDER_CPU_GROUP + 88;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0                       http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <artifactId>${artifactId}</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>${pluginDescription} - CLI</name>
+
+  <dependencies>
+      
+      <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>org.osgi.core</artifactId>
+          <scope>provided</scope>
+      </dependency>
+
+      <!-- thermostat specific dependencies -->
+    
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-client-core</artifactId>
+          <type>jar</type>
+      </dependency>
+      
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-common-core</artifactId>
+          <type>jar</type>
+      </dependency>
+    
+      <!-- plugin specific dependencies -->
+      
+      <dependency>
+          <groupId>${groupId}</groupId>
+          <artifactId>${rootArtifactId}-storage-common</artifactId>
+    	  <version>${version}</version>
+      </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+    
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>${package}.cli.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>${package}.cli</Bundle-SymbolicName>
+            <Private-Package>${package}.cli.internal</Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+      
+      <!-- No tests in this module, skip them in order to make the build pass -->    
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>true</skipTests>
+        </configuration>
+      </plugin>
+      
+    </plugins>
+
+  </build>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/src/main/java/cli/internal/Activator.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,29 @@
+package ${package}.cli.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import com.redhat.thermostat.common.cli.CommandRegistry;
+import com.redhat.thermostat.common.cli.CommandRegistryImpl;
+
+/**
+ * Registers the {@link ExampleCommand} with Thermostat.
+ */
+public class Activator implements BundleActivator {
+
+    private CommandRegistry reg;
+    
+    @Override
+    public void start(BundleContext context) throws Exception {    
+        ExampleCommand cmd = new ExampleCommand(context);
+        reg = new CommandRegistryImpl(context);
+        reg.registerCommand(ExampleCommand.NAME, cmd);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        if (reg != null) {
+            // unregisters commands which this registry registered
+            reg.unregisterCommands();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/client-cli/src/main/java/cli/internal/ExampleCommand.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,78 @@
+package ${package}.cli.internal;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import com.redhat.thermostat.common.cli.AbstractCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.HostInfo;
+import ${package}.storage.ExampleDAO;
+
+public class ExampleCommand extends AbstractCommand {
+
+    public static final String NAME = "example-command";
+    private static final String AGENT_ARG = "hostId";
+    
+    private final BundleContext context;
+    
+    ExampleCommand(BundleContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public void run(CommandContext ctxt) throws CommandException {
+        Arguments args = ctxt.getArguments();
+        if (!args.hasArgument(AGENT_ARG)) {
+            throw new CommandException(new LocalizedString("Agent argument required!"));
+        }
+        AgentInfoDAO agentInfo = getDaoService(AgentInfoDAO.class);
+        if (agentInfo == null) {
+            throw new CommandException(new LocalizedString("AgentInfoDAO unavaialable!"));
+        }
+        String agentId = args.getArgument(AGENT_ARG);
+        
+        // Use our DAO to resolve the agent, i.e. host
+        HostRef hostRef = new HostRef(agentId, "not-used");
+        AgentInformation resolvedAgent = agentInfo.getAgentInformation(hostRef);
+        if (resolvedAgent == null) {
+            throw new CommandException(new LocalizedString("Unknown agentId: " + agentId));
+        }
+        HostInfoDAO hostInfo = getDaoService(HostInfoDAO.class);
+        if (hostInfo == null) {
+            throw new CommandException(new LocalizedString("HostInfoDAO unavaialable!"));
+        }
+        HostInfo info = hostInfo.getHostInfo(hostRef);
+        ExampleDAO exampleDAO = getDaoService(ExampleDAO.class);
+        if (exampleDAO == null) {
+            throw new CommandException(new LocalizedString("ExampleDAO unavailable"));
+        }
+        String message = exampleDAO.getMessage(hostRef);
+        if (message == null) {
+            message = "something's fishy :)";
+        }
+        // Here one should use appropriate formatters instead. Shame on me :(
+        ctxt.getConsole().getOutput().println("Host: " + info.getHostname());
+        ctxt.getConsole().getOutput().println("Message: " + message);
+    }
+    
+    private <T> T getDaoService(Class<T> clazz) {
+        ServiceReference ref = context.getServiceReference(clazz.getName());
+        @SuppressWarnings("unchecked")
+        T service = (T) context.getService(ref);
+        return service;
+    }
+    
+    @Override
+    public boolean isStorageRequired() {
+        // Let the launcher connect to storage for us
+        return true;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/deploy.sh	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,18 @@
+#/bin/bash
+if [ x"$THERMOSTAT_DEV_HOME" = x ] ; then
+    echo "variable THERMOSTAT_DEV_HOME is not set, should point to the thermostat src directory"
+    exit -1
+fi
+
+PLUGIN_DIR=$THERMOSTAT_DEV_HOME/distribution/target/image/plugins
+
+TARGET_DIR="${pluginDeployDir}"
+
+if [ -e $PLUGIN_DIR/$TARGET_DIR ]; then
+    rm -rf $PLUGIN_DIR/$TARGET_DIR
+fi
+
+DISTRO_ZIP=$(pwd)/distribution/target/${artifactId}-distribution*.zip
+pushd $PLUGIN_DIR
+unzip $DISTRO_ZIP
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/distribution/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012 - 2014 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>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <artifactId>${artifactId}</artifactId>
+  <packaging>pom</packaging>
+
+  <!-- Assembles the jars of this plugin in a zip file for easy
+       deployment. see target/${rootArtifactId}*.zip
+       after a maven build -->
+  <name>${pluginDescription} - Distribution</name>
+  
+  <properties>
+    <thermostat.plugin>${pluginDeployDir}</thermostat.plugin>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <dependencies>
+          <dependency>
+            <groupId>com.redhat.thermostat</groupId>
+            <artifactId>thermostat-assembly</artifactId>
+            <version>${thermostat-core-version}</version>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <descriptorRefs>
+            <descriptorRef>plugin-assembly</descriptorRef>
+          </descriptorRefs>
+          <appendAssemblyId>false</appendAssemblyId>
+        </configuration>
+        <executions>
+          <execution>
+            <id>assemble-plugin</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  
+  <!-- Explicitly list all plug-in artifacts, transitive dependencies
+       are not included in assembly. -->
+  <dependencies>
+    <dependency>
+      <groupId>${groupId}</groupId>
+      <artifactId>${rootArtifactId}-agent</artifactId>
+      <version>${version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${groupId}</groupId>
+      <artifactId>${rootArtifactId}-storage-common</artifactId>
+      <version>${version}</version>
+    </dependency>
+    <dependency>
+      <groupId>${groupId}</groupId>
+      <artifactId>${rootArtifactId}-cli</artifactId>
+      <version>${version}</version>
+    </dependency>
+  </dependencies>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/distribution/thermostat-plugin.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,55 @@
+#set( $symbol_dollar = '$' )
+<?xml version="1.0"?>
+<plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <commands>
+    <command>
+      <name>example-command</name>
+      <description>Show the message for a given agent</description>
+      <options>
+        <option>
+          <long>hostId</long>
+          <short>a</short>
+          <argument>hostId</argument>
+          <required>true</required>
+          <description>the ID of the agent to show the message for</description>
+        </option>
+        <option common="true">
+          <long>dbUrl</long>
+        </option>
+        <option common="true">
+          <long>logLevel</long>
+        </option>
+      </options>
+      <environments>
+        <environment>cli</environment>
+        <environment>shell</environment>
+      </environments>
+      <bundles>
+        <bundle><symbolic-name>${package}.cli</symbolic-name><version>${symbol_dollar}{project.version}</version></bundle>
+        <bundle><symbolic-name>${package}.storage</symbolic-name><version>${symbol_dollar}{project.version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.storage.core</symbolic-name><version>${symbol_dollar}{thermostat-core-version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.storage.mongodb</symbolic-name><version>${symbol_dollar}{thermostat-core-version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.web.common</symbolic-name><version>${symbol_dollar}{thermostat-core-version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.web.client</symbolic-name><version>${symbol_dollar}{thermostat-core-version}</version></bundle>
+        <bundle><symbolic-name>org.mongodb.mongo-java-driver</symbolic-name><version>${symbol_dollar}{mongo-driver.osgi-version}</version></bundle>
+        <bundle><symbolic-name>org.apache.commons.beanutils</symbolic-name><version>${symbol_dollar}{commons-beanutils.version}</version></bundle>
+        <bundle><symbolic-name>org.apache.commons.codec</symbolic-name><version>${symbol_dollar}{commons-codec.osgi-version}</version></bundle>
+        <bundle><symbolic-name>org.apache.commons.collections</symbolic-name><version>${symbol_dollar}{commons-collections.version}</version></bundle>
+        <bundle><symbolic-name>org.apache.commons.logging</symbolic-name><version>${symbol_dollar}{commons-logging.version}</version></bundle>
+        <bundle><symbolic-name>org.apache.httpcomponents.httpcore</symbolic-name><version>${symbol_dollar}{httpcomponents.core.version}</version></bundle>
+        <bundle><symbolic-name>org.apache.httpcomponents.httpclient</symbolic-name><version>${symbol_dollar}{httpcomponents.client.version}</version></bundle>
+        <bundle><symbolic-name>com.google.gson</symbolic-name><version>${symbol_dollar}{gson.version}</version></bundle>
+      </bundles>
+    </command>
+  </commands>
+  <extensions>
+   <extension>
+      <name>agent</name>
+      <bundles>
+        <bundle><symbolic-name>${package}.storage</symbolic-name><version>${symbol_dollar}{project.version}</version></bundle>
+        <bundle><symbolic-name>${package}.agent</symbolic-name><version>${symbol_dollar}{project.version}</version></bundle>
+      </bundles>
+    </extension>
+  </extensions>
+</plugin>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,155 @@
+#set( $symbol_dollar = '$' )
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012 - 2014 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>
+
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <version>${version}</version>
+  <packaging>pom</packaging>
+
+  <name>${pluginDescription}</name>
+
+  <properties>
+  
+    <main.basedir>${symbol_dollar}{project.basedir}</main.basedir>  
+  
+    <maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <thermostat.java.version>1.7</thermostat.java.version>
+    <junit.version>4.10</junit.version>
+
+    <osgi.core.version>4.2.0</osgi.core.version>
+    <felix.framework.version>4.0.2</felix.framework.version>
+    <!-- version of core thermostat we'll be using -->
+    <thermostat-core-version>${thermostat-core-version}</thermostat-core-version>
+    
+    <!-- Dependencies which core thermostat uses -->
+    <mongo-driver.osgi-version>2.11.2.RELEASE</mongo-driver.osgi-version>
+    <commons-beanutils.version>1.8.3</commons-beanutils.version>
+    <commons-codec.osgi-version>1.7.0</commons-codec.osgi-version>
+    <commons-collections.version>3.2.1</commons-collections.version>
+    <commons-logging.version>1.1.3</commons-logging.version>
+    <httpcomponents.core.version>4.1.2</httpcomponents.core.version>
+    <httpcomponents.client.version>4.1.2</httpcomponents.client.version>
+    <gson.version>2.2.2</gson.version>
+    
+    
+  </properties>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.3.2</version>
+          <configuration>
+            <source>${thermostat.java.version}</source>
+            <target>${thermostat.java.version}</target>
+          </configuration>
+        </plugin>
+ 
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.12</version>
+          <dependencies>
+            <dependency>
+              <groupId>org.apache.maven.surefire</groupId>
+              <artifactId>surefire-junit47</artifactId>
+              <version>2.12</version>
+            </dependency>
+          </dependencies>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <version>1.4.0</version>
+        </plugin>
+
+    </plugins>
+   </pluginManagement>
+  </build>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>${symbol_dollar}{junit.version}</version>
+      </dependency>
+      
+      <dependency>
+        <groupId>org.osgi</groupId>
+        <artifactId>org.osgi.core</artifactId>
+        <version>${symbol_dollar}{osgi.core.version}</version>
+      </dependency>
+    
+      <!-- thermostat dependencies -->
+
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-common-core</artifactId>
+          <version>${symbol_dollar}{thermostat-core-version}</version>
+      </dependency>
+
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-storage-core</artifactId>
+          <version>${symbol_dollar}{thermostat-core-version}</version>
+      </dependency>
+      
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-agent-core</artifactId>
+          <version>${symbol_dollar}{thermostat-core-version}</version>
+      </dependency>
+        
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-client-core</artifactId>
+          <version>${symbol_dollar}{thermostat-core-version}</version>
+      </dependency>
+    
+    </dependencies>
+  </dependencyManagement>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,105 @@
+#set( $symbol_dollar = '$' )
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012, 2013 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>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <artifactId>${artifactId}</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>${pluginDescription} - Storage Common</name>
+
+  <dependencies>
+      
+      <!-- osgi and test dependencies -->
+      
+      <dependency>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+          <scope>test</scope>
+      </dependency>
+        
+      <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>org.osgi.core</artifactId>
+          <scope>provided</scope>
+      </dependency>
+
+      <!-- thermostat specific dependencies -->
+    
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-storage-core</artifactId>
+      </dependency>
+      
+      <dependency>
+          <groupId>com.redhat.thermostat</groupId>
+          <artifactId>thermostat-common-core</artifactId>
+      </dependency>
+    
+      <!-- plugin specific dependencies -->
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${package}.storage</Bundle-SymbolicName>
+            <Bundle-Activator>${package}.storage.internal.Activator</Bundle-Activator>
+            <Export-Package>${package}.storage</Export-Package>
+            <Private-Package>${package}.storage.internal</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/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/ExampleDAO.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 - 2014 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 ${package}.storage;
+
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.Key;
+
+public interface ExampleDAO {
+    
+    public static final Key<String> MESSAGE_KEY = new Key<>("message");
+
+    /*
+     * Schema description for the messages collection
+     */
+    static final Category<ExampleMessage> exampleCategory = new Category<>("example-messages",
+                                                                            ExampleMessage.class,
+                                                                            Key.AGENT_ID,
+                                                                            MESSAGE_KEY);
+
+    void putMessage(String message);
+    
+    String getMessage(HostRef ref);
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/ExampleMessage.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,51 @@
+package ${package}.storage;
+
+import java.util.Objects;
+
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.BasePojo;
+
+/**
+ * This is the model class which gets persisted
+ *
+ */
+@Entity
+public class ExampleMessage extends BasePojo {
+    
+    private String message;
+    
+    public ExampleMessage(String writerId) {
+        super(writerId);
+    }
+    
+    // Used for JSON serialization. Don't
+    // explicitly use it.
+    public ExampleMessage() {
+        this(null);
+    }
+    
+    @Persist
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @Persist
+    public String getMessage() {
+        return message;
+    }
+
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), message);
+    }
+    
+    public boolean equals(Object other) {
+        if (!(other instanceof ExampleMessage)) {
+            return false;
+        }
+        ExampleMessage o = (ExampleMessage)other;
+        return super.equals(o) &&
+                message.equals(o.message);
+                
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/internal/Activator.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,65 @@
+package ${package}.storage.internal;
+
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.WriterID;
+import ${package}.storage.ExampleDAO;
+
+public class Activator implements BundleActivator {
+    
+    private MultipleServiceTracker multiTracker;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        Class<?>[] dependentServices = new Class[] {
+                Storage.class,
+                WriterID.class
+        };
+        // Track Storage and WriterID and register our new DAO once services
+        // become available.
+        ServicesAvailableAction action = new ServicesAvailableAction(context);
+        multiTracker = new MultipleServiceTracker(context,
+                                                  dependentServices,
+                                                  action);
+        multiTracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        multiTracker.close();
+    }
+    
+    private static final class ServicesAvailableAction implements Action {
+
+        private final BundleContext context;
+        private ServiceRegistration reg;
+        
+        private ServicesAvailableAction(BundleContext context) {
+            this.context = context;
+        }
+        
+        @Override
+        public void dependenciesAvailable(Map<String, Object> services) {
+            Storage storage = (Storage)services.get(Storage.class.getName());
+            WriterID writerId = (WriterID)services.get(WriterID.class.getName());
+            ExampleDAO daoImpl = new ExampleDAOImpl(storage, writerId);
+            reg = context.registerService(ExampleDAO.class.getName(), daoImpl, null);
+        }
+
+        @Override
+        public void dependenciesUnavailable() {
+            if (reg != null) {
+                reg.unregister();
+            }
+        }
+        
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/main/java/storage/internal/ExampleDAOImpl.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012 - 2014, 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 ${package}.storage.internal;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.WriterID;
+import ${package}.storage.ExampleMessage;
+import ${package}.storage.ExampleDAO;
+
+public class ExampleDAOImpl implements ExampleDAO {
+    
+    private static final Logger logger = LoggingUtils.getLogger(ExampleDAOImpl.class);
+
+    private static final String REPLACE_DESCRIPTOR = "REPLACE " + exampleCategory.getName() +
+                                                              " SET '" + Key.AGENT_ID.getName() + "' = ?s , " +
+                                                                   "'" + MESSAGE_KEY.getName() + "' = ?s " +
+                                                                   "WHERE '" + Key.AGENT_ID.getName() + "' = ?s";
+    private static final String QUERY_DESCRIPTOR = "QUERY " + exampleCategory.getName() + " WHERE " +
+                                                                   "'" + Key.AGENT_ID.getName() + "' = ?s";
+    
+    private final Storage storage;
+    private final WriterID writerId;
+    
+    ExampleDAOImpl(Storage storage, WriterID writerId) {
+        this.storage = storage;
+        this.writerId = writerId;
+        storage.registerCategory(exampleCategory);
+    }
+    
+    @Override
+    public void putMessage(String message) {
+        try {
+            StatementDescriptor<ExampleMessage> stmtDesc = new StatementDescriptor<>(exampleCategory, REPLACE_DESCRIPTOR);
+            PreparedStatement<ExampleMessage> replace = storage.prepareStatement(stmtDesc);
+            replace.setString(0, writerId.getWriterID());
+            replace.setString(1, message);
+            replace.setString(2, writerId.getWriterID());
+            replace.execute();
+        } catch (DescriptorParsingException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Preparing '" + REPLACE_DESCRIPTOR + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Executing '" + REPLACE_DESCRIPTOR + "' failed!", e);
+        }
+    }
+    
+    @Override
+    public String getMessage(HostRef ref) {
+        Cursor<ExampleMessage> cursor = null;
+        try {
+            StatementDescriptor<ExampleMessage> stmtDesc = new StatementDescriptor<>(exampleCategory, QUERY_DESCRIPTOR);
+            PreparedStatement<ExampleMessage> query = storage.prepareStatement(stmtDesc);
+            query.setString(0, ref.getAgentId());
+            cursor = query.executeQuery();
+        } catch (DescriptorParsingException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Preparing '" + QUERY_DESCRIPTOR + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Executing '" + QUERY_DESCRIPTOR + "' failed!", e);
+        }
+        if (cursor == null) {
+            return null;
+        }
+        if (cursor.hasNext()) {
+            ExampleMessage pojo = cursor.next();
+            return pojo.getMessage();
+        } else {
+            return null;
+        }
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/main/resources/archetype-resources/storage-common/src/test/java/storage/ExampleMessageTest.java	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,44 @@
+package ${package}.storage;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ExampleMessageTest {
+
+    @Test
+    public void testEquals() {
+        ExampleMessage message = new ExampleMessage();
+        message.setAgentId("foo");
+        message.setMessage("bar");
+        
+        assertTrue(message.equals(message));
+        ExampleMessage message2 = new ExampleMessage();
+        assertFalse(message.equals(message2));
+        assertFalse(message.equals("I'm a string"));
+        
+        message2.setAgentId("foo");
+        assertFalse(message.equals(message2));
+        message2.setMessage("bar");
+        assertTrue(message.equals(message2));
+    }
+    
+    @Test
+    public void testHashCode() {
+        ExampleMessage message = new ExampleMessage();
+        message.setAgentId("foo");
+        message.setMessage("bar");
+        
+        assertTrue(message.hashCode() == message.hashCode());
+        ExampleMessage message2 = new ExampleMessage();
+        assertFalse(message.hashCode() == message2.hashCode());
+        assertFalse(message.hashCode() == "I'm a string".hashCode());
+        
+        message2.setAgentId("foo");
+        assertFalse(message.hashCode() == message2.hashCode());
+        message2.setMessage("bar");
+        assertTrue(message.hashCode() == message2.hashCode());
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/multi-module-plugin-archetype/src/test/resources/projects/basic/archetype.properties	Tue Jan 21 19:17:27 2014 +0100
@@ -0,0 +1,8 @@
+#Mon Jan 20 17:42:19 CET 2014
+package=com.example.thermostat.plugins
+version=0.0.1-SNAPSHOT
+groupId=com.example.thermostat.plugins
+artifactId=thermostat-example-plugin
+pluginDescription=Thermostat example plug-in
+helloMessage=Hello World!
+pluginDeployDir=example-plugin
\ No newline at end of file
--- a/dev/pom.xml	Mon Feb 10 19:08:50 2014 -0500
+++ b/dev/pom.xml	Tue Jan 21 19:17:27 2014 +0100
@@ -52,6 +52,7 @@
 
   <modules>
     <module>archetype-ext</module>
+    <module>multi-module-plugin-archetype</module>
   </modules>
 
 </project>