changeset 3:ccb2cb262665

Add base for plugin http handler. Add assembly file to build zip of plugin.
author Jie Kang <jkang@redhat.com>
date Mon, 06 Feb 2017 13:08:04 -0500
parents a2e85e95c5fb
children e45a8fb2bf50
files server/core/src/main/java/com/redhat/thermostat/server/core/CoreServer.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/CoreHttpHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/HttpHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/PluginHttpHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/CoreStorageHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoCoreStorageHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoPluginStorageHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoStorageHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/PluginStorageHandler.java server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/StorageHandler.java server/distribution/pom.xml server/distribution/src/assembly/src.xml
diffstat 12 files changed, 676 insertions(+), 368 deletions(-) [+]
line wrap: on
line diff
--- a/server/core/src/main/java/com/redhat/thermostat/server/core/CoreServer.java	Thu Feb 02 15:37:43 2017 -0500
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/CoreServer.java	Mon Feb 06 13:08:04 2017 -0500
@@ -26,8 +26,8 @@
 import com.redhat.thermostat.server.core.internal.security.UserStore;
 import com.redhat.thermostat.server.core.internal.security.auth.proxy.ProxyAuthFilter;
 import com.redhat.thermostat.server.core.internal.storage.ThermostatMongoStorage;
-import com.redhat.thermostat.server.core.internal.web.handler.http.HttpHandler;
-import com.redhat.thermostat.server.core.internal.web.handler.storage.MongoStorageHandler;
+import com.redhat.thermostat.server.core.internal.web.handler.http.CoreHttpHandler;
+import com.redhat.thermostat.server.core.internal.web.handler.storage.MongoCoreStorageHandler;
 
 @Component
 @Service(CoreServer.class)
@@ -50,7 +50,7 @@
     }
 
     private void setupResourceConfig(Map<String, String> serverConfig, Map<String, String> userConfig, ResourceConfig resourceConfig) {
-        resourceConfig.register(new HttpHandler(new MongoStorageHandler()));
+        resourceConfig.register(new CoreHttpHandler(new MongoCoreStorageHandler()));
         resourceConfig.register(new ProxyAuthFilter(new UserStore(userConfig)));
         resourceConfig.register(new RolesAllowedDynamicFeature());
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/CoreHttpHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012-2017 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.server.core.internal.web.handler.http;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.glassfish.jersey.server.ChunkedOutput;
+
+import com.redhat.thermostat.server.core.internal.web.handler.storage.CoreStorageHandler;
+
+@Path("/api")
+@RolesAllowed("user")
+public class CoreHttpHandler {
+
+    private final CoreStorageHandler handler;
+
+    public CoreHttpHandler(CoreStorageHandler handler) {
+        this.handler = handler;
+    }
+
+    @GET
+    @Path("agents/{agentId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void getAgent(@Context SecurityContext securityContext,
+                         @Suspended final AsyncResponse asyncResponse,
+                         @PathParam("agentId") String agentId,
+                         @QueryParam("size") @DefaultValue("1") String count,
+                         @QueryParam("sort") @DefaultValue("-1") String sort) {
+        handler.getAgent(securityContext, asyncResponse, agentId, count, sort);
+    }
+
+    @PUT
+    @Path("agents/{agentId}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response putAgent(String body,
+                             @Context SecurityContext context) {
+        return handler.putAgent(body, context);
+    }
+
+    @GET
+    @Path("agents/{agentId}/host/cpu")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getHostCpuInfo(@Context SecurityContext securityContext,
+                                   @PathParam("agentId") String agentId,
+                                   @QueryParam("size") @DefaultValue("1") String count,
+                                   @QueryParam("sort") @DefaultValue("-1") String sort,
+                                   @QueryParam("maxTimestamp") String maxTimestamp,
+                                   @QueryParam("minTimestamp") String minTimestamp) {
+        return handler.getHostCpuInfo(securityContext, agentId, count, sort, maxTimestamp, minTimestamp);
+    }
+
+    @GET
+    @Path("stream/agents/{agentId}/host/cpu")
+    @Produces(MediaType.APPLICATION_JSON)
+    public ChunkedOutput<String> streamHostCpuInfo(@Context SecurityContext securityContext,
+                                                   @PathParam("agentId") String agentId) {
+        return handler.streamHostCpuInfo(securityContext, agentId);
+    }
+}
\ No newline at end of file
--- a/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/HttpHandler.java	Thu Feb 02 15:37:43 2017 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/*
- * Copyright 2012-2017 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.server.core.internal.web.handler.http;
-
-import javax.annotation.security.RolesAllowed;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.container.Suspended;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
-
-import org.glassfish.jersey.server.ChunkedOutput;
-
-import com.redhat.thermostat.server.core.internal.web.handler.storage.StorageHandler;
-
-@Path("/api")
-@RolesAllowed("user")
-public class HttpHandler {
-
-    private final StorageHandler handler;
-
-    public HttpHandler(StorageHandler handler) {
-        this.handler = handler;
-    }
-
-    @GET
-    @Path("agents/{agentId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public void getAgent(@Context SecurityContext securityContext,
-                         @Suspended final AsyncResponse asyncResponse,
-                         @PathParam("agentId") String agentId,
-                         @QueryParam("size") @DefaultValue("1") String count,
-                         @QueryParam("sort") @DefaultValue("-1") String sort) {
-        handler.getAgent(securityContext, asyncResponse, agentId, count, sort);
-    }
-
-    @PUT
-    @Path("agents/{agentId}")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response putAgent(String body,
-                             @Context SecurityContext context) {
-        return handler.putAgent(body, context);
-    }
-
-    @GET
-    @Path("agents/{agentId}/host/cpu")
-    @Produces(MediaType.APPLICATION_JSON)
-    public Response getHostCpuInfo(@Context SecurityContext securityContext,
-                                   @PathParam("agentId") String agentId,
-                                   @QueryParam("size") @DefaultValue("1") String count,
-                                   @QueryParam("sort") @DefaultValue("-1") String sort,
-                                   @QueryParam("maxTimestamp") String maxTimestamp,
-                                   @QueryParam("minTimestamp") String minTimestamp) {
-        return handler.getHostCpuInfo(securityContext, agentId, count, sort, maxTimestamp, minTimestamp);
-    }
-
-    @GET
-    @Path("stream/agents/{agentId}/host/cpu")
-    @Produces(MediaType.APPLICATION_JSON)
-    public ChunkedOutput<String> streamHostCpuInfo(@Context SecurityContext securityContext,
-                                                   @PathParam("agentId") String agentId) {
-        return handler.streamHostCpuInfo(securityContext, agentId);
-    }
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/http/PluginHttpHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,145 @@
+package com.redhat.thermostat.server.core.internal.web.handler.http;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.SecurityContext;
+
+import org.glassfish.jersey.server.ChunkedOutput;
+
+import com.redhat.thermostat.server.core.internal.web.handler.storage.PluginStorageHandler;
+
+@Path("/plugin")
+@RolesAllowed("user")
+public class PluginHttpHandler {
+    private final PluginStorageHandler handler;
+
+    public PluginHttpHandler(PluginStorageHandler handler) {
+        this.handler = handler;
+    }
+
+    /**
+     * Get information about the path.
+     */
+    @GET
+    @Path("/{path}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void getPath(@Context SecurityContext securityContext,
+                        @Suspended final AsyncResponse asyncResponse,
+                        @PathParam("path") String path) {
+        handler.getPath(securityContext, asyncResponse, path);
+    }
+
+    /**
+     * Add one or more items to the path
+     */
+    @PUT
+    @Path("/{path}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void putPath(@Context SecurityContext securityContext,
+                        @Suspended final AsyncResponse asyncResponse,
+                        @PathParam("path") String path,
+                        String body) {
+        handler.putPath(securityContext, asyncResponse, path, body);
+    }
+
+    /**
+     * Query the path for items
+     */
+    @POST
+    @Path("/{path}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void postPath(@Context SecurityContext securityContext,
+                         @Suspended final AsyncResponse asyncResponse,
+                         @PathParam("path") String path,
+                         String body) {
+        handler.postPath(securityContext, asyncResponse, path, body);
+    }
+
+    /**
+     * Delete the entire path
+     */
+    @DELETE
+    @Path("/{path}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void deletePath(@Context SecurityContext securityContext,
+                           @Suspended final AsyncResponse asyncResponse,
+                           @PathParam("path") String path) {
+        handler.deletePath(securityContext, asyncResponse, path);
+    }
+
+    /**
+     * Stream items from the path
+     */
+    @GET
+    @Path("stream/{path}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public ChunkedOutput<String> streamPath(@Context SecurityContext securityContext,
+                                    @Suspended final AsyncResponse asyncResponse,
+                                    @PathParam("path") String path) {
+        return handler.streamPath(securityContext, asyncResponse, path);
+    }
+
+    /**
+     * Get the item
+     */
+    @GET
+    @Path("/{path}/{item}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void getPathItem(@Context SecurityContext securityContext,
+                            @Suspended final AsyncResponse asyncResponse,
+                            @PathParam("path") String path,
+                            @PathParam("item") String item) {
+        handler.getPathItem(securityContext, asyncResponse, path, item);
+    }
+
+    /**
+     * Create or replace the item
+     */
+    @PUT
+    @Path("/{path}/{item}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void putPathItem(@Context SecurityContext securityContext,
+                            @Suspended final AsyncResponse asyncResponse,
+                            @PathParam("path") String path,
+                            @PathParam("item") String item,
+                            String body) {
+        handler.putPathItem(securityContext, asyncResponse, path, item, body);
+    }
+
+    /**
+     * Query the item
+     */
+    @POST
+    @Path("/{path}/{item}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void postPathItem(@Context SecurityContext securityContext,
+                             @Suspended final AsyncResponse asyncResponse,
+                             @PathParam("path") String path,
+                             @PathParam("item") String item,
+                             String body) {
+        handler.postPathItem(securityContext, asyncResponse, path, item, body);
+    }
+
+    /**
+     * Delete the item
+     */
+    @DELETE
+    @Path("/{path}/{item}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public void deletePathItem(@Context SecurityContext securityContext,
+                               @Suspended final AsyncResponse asyncResponse,
+                               @PathParam("path") String path,
+                               @PathParam("item") String item) {
+        handler.deletePathItem(securityContext, asyncResponse, path, item);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/CoreStorageHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012-2017 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.server.core.internal.web.handler.storage;
+
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.glassfish.jersey.server.ChunkedOutput;
+
+public interface CoreStorageHandler {
+    void getAgent(SecurityContext securityContext,
+                  AsyncResponse asyncResponse,
+                  String agentId,
+                  String count,
+                  String sort);
+
+    Response putAgent(String body,
+                      SecurityContext context);
+
+    Response getHostCpuInfo(SecurityContext securityContext,
+                            String agentId,
+                            String count,
+                            String sort,
+                            String maxTimestamp,
+                            String minTimestamp);
+
+    ChunkedOutput<String> streamHostCpuInfo(SecurityContext securityContext,
+                                            String agentId);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoCoreStorageHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2012-2017 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.server.core.internal.web.handler.storage;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.glassfish.jersey.server.ChunkedOutput;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.client.FindIterable;
+import com.redhat.thermostat.server.core.internal.storage.ThermostatMongoStorage;
+import com.redhat.thermostat.server.core.internal.web.filters.RequestFilters;
+import com.redhat.thermostat.server.core.internal.web.json.DocumentBuilder;
+import com.redhat.thermostat.server.core.internal.web.request.TimedRequest;
+import com.redhat.thermostat.server.core.internal.web.response.MongoResponseBuilder;
+
+public class MongoCoreStorageHandler implements CoreStorageHandler {
+
+    private final int MAX_MONGO_DOCUMENTS = 5000;
+
+    @Override
+    public void getAgent(final SecurityContext securityContext,
+                         final AsyncResponse asyncResponse,
+                         final String agentId,
+                         final String count,
+                         final String sort) {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                if (!ThermostatMongoStorage.isConnected()) {
+                    asyncResponse.resume(Response.status(Response.Status.OK).entity("GET " + agentId + " " + count + " " + sort + " " + securityContext.getUserPrincipal().getName()).build());
+                    return;
+                }
+
+                final int limit = Math.min(Integer.valueOf(count), MAX_MONGO_DOCUMENTS);
+                final int sortOrder = Integer.valueOf(sort);
+                final String userName = securityContext.getUserPrincipal().getName();
+                final Bson filter = RequestFilters.buildGetFilter(agentId, Collections.singletonList(userName));
+
+                TimedRequest<FindIterable<Document>> timedRequest = new TimedRequest<>();
+
+                FindIterable<Document> documents = timedRequest.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
+                    @Override
+                    public FindIterable<Document> run() {
+                        return ThermostatMongoStorage.getDatabase().getCollection("agents").find(filter).sort(new BasicDBObject("_id", sortOrder)).limit(limit);
+                    }
+                });
+
+                asyncResponse.resume(Response.status(Response.Status.OK).entity(MongoResponseBuilder.buildJsonResponse(documents, timedRequest.getElapsed())).build());
+            }
+        }).start();
+    }
+
+    @Override
+    public Response putAgent(String body,
+                             @Context SecurityContext context) {
+        if (!ThermostatMongoStorage.isConnected()) {
+            return Response.status(Response.Status.OK).entity("PUT " + context.getUserPrincipal().getName() + "\n\n" + body).build();
+        }
+
+        TimedRequest<FindIterable<Document>> timedRequest = new TimedRequest<>();
+
+        /*
+         * TODO: Verify body matches expected schema
+         * TODO: Clean up insertion of tags into JSON body
+         */
+        final Document item = Document.parse(DocumentBuilder.addTags(body, context.getUserPrincipal().getName()));
+
+        timedRequest.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
+            @Override
+            public FindIterable<Document> run() {
+                ThermostatMongoStorage.getDatabase().getCollection("agents").insertOne(item);
+                return null;
+            }
+        });
+
+        return Response.status(Response.Status.OK).entity("PUT successful").build();
+    }
+
+    @Override
+    public Response getHostCpuInfo(SecurityContext securityContext,
+                                   String agentId,
+                                   String count,
+                                   String sort,
+                                   String maxTimestamp,
+                                   String minTimestamp) {
+        if (!ThermostatMongoStorage.isConnected()) {
+            return Response.status(Response.Status.OK).entity(agentId + count + sort + maxTimestamp + minTimestamp).build();
+        }
+
+        final int size = Integer.valueOf(count);
+
+        final String userName = securityContext.getUserPrincipal().getName();
+        final Bson filter = RequestFilters.buildGetFilter(agentId, Collections.singletonList(userName), maxTimestamp, minTimestamp);
+
+        final int sortOrder = Integer.valueOf(sort);
+
+        TimedRequest<FindIterable<Document>> request = new TimedRequest<>();
+        FindIterable<Document> documents = request.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
+            @Override
+            public FindIterable<Document> run() {
+                return ThermostatMongoStorage.getDatabase().getCollection("cpu-stats").find(filter).sort(new BasicDBObject("_id", sortOrder)).limit(size);
+            }
+        });
+
+        return Response.status(Response.Status.OK).entity(MongoResponseBuilder.buildJsonResponse(documents, request.getElapsed())).build();
+    }
+
+    @Override
+    public ChunkedOutput<String> streamHostCpuInfo(SecurityContext securityContext, String agentId) {
+        final ChunkedOutput<String> output = new ChunkedOutput<>(String.class, "\r\n");
+
+        new Thread() {
+            public void run() {
+                try {
+
+                    while (true) {
+                        TimedRequest<FindIterable<Document>> request = new TimedRequest<>();
+                        FindIterable<Document> documents = request.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
+                            @Override
+                            public FindIterable<Document> run() {
+                                return ThermostatMongoStorage.getDatabase().getCollection("cpu-stats").find().sort(new BasicDBObject("_id", -1)).limit(1);
+                            }
+                        });
+                        output.write(MongoResponseBuilder.buildJsonResponse(documents, request.getElapsed()));
+
+                        Thread.sleep(1000L);
+                    }
+                } catch (IOException | InterruptedException e) {
+                    // An IOException occurs when reader closes the connection
+                } finally {
+                    try {
+                        output.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }.start();
+
+        return output;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoPluginStorageHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,53 @@
+package com.redhat.thermostat.server.core.internal.web.handler.storage;
+
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.core.SecurityContext;
+
+import org.glassfish.jersey.server.ChunkedOutput;
+
+public class MongoPluginStorageHandler implements PluginStorageHandler {
+    @Override
+    public void getPath(SecurityContext securityContext, AsyncResponse asyncResponse, String path) {
+
+    }
+
+    @Override
+    public void putPath(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String body) {
+
+    }
+
+    @Override
+    public void postPath(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String body) {
+
+    }
+
+    @Override
+    public void deletePath(SecurityContext securityContext, AsyncResponse asyncResponse, String path) {
+
+    }
+
+    @Override
+    public ChunkedOutput<String> streamPath(SecurityContext securityContext, AsyncResponse asyncResponse, String path) {
+        return null;
+    }
+
+    @Override
+    public void getPathItem(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String item) {
+
+    }
+
+    @Override
+    public void putPathItem(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String item, String body) {
+
+    }
+
+    @Override
+    public void postPathItem(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String item, String body) {
+
+    }
+
+    @Override
+    public void deletePathItem(SecurityContext securityContext, AsyncResponse asyncResponse, String path, String item) {
+
+    }
+}
--- a/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/MongoStorageHandler.java	Thu Feb 02 15:37:43 2017 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-/*
- * Copyright 2012-2017 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.server.core.internal.web.handler.storage;
-
-import java.io.IOException;
-import java.util.Collections;
-
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
-
-import org.bson.Document;
-import org.bson.conversions.Bson;
-import org.glassfish.jersey.server.ChunkedOutput;
-
-import com.mongodb.BasicDBObject;
-import com.mongodb.client.FindIterable;
-import com.redhat.thermostat.server.core.internal.storage.ThermostatMongoStorage;
-import com.redhat.thermostat.server.core.internal.web.filters.RequestFilters;
-import com.redhat.thermostat.server.core.internal.web.json.DocumentBuilder;
-import com.redhat.thermostat.server.core.internal.web.request.TimedRequest;
-import com.redhat.thermostat.server.core.internal.web.response.MongoResponseBuilder;
-
-public class MongoStorageHandler implements StorageHandler {
-
-    private final int MAX_MONGO_DOCUMENTS = 5000;
-
-    @Override
-    public void getAgent(final SecurityContext securityContext,
-                         final AsyncResponse asyncResponse,
-                         final String agentId,
-                         final String count,
-                         final String sort) {
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                if (!ThermostatMongoStorage.isConnected()) {
-                    asyncResponse.resume(Response.status(Response.Status.OK).entity("GET " + agentId + " " + count + " " + sort + " " + securityContext.getUserPrincipal().getName()).build());
-                    return;
-                }
-
-                final int limit = Math.min(Integer.valueOf(count), MAX_MONGO_DOCUMENTS);
-                final int sortOrder = Integer.valueOf(sort);
-                final String userName = securityContext.getUserPrincipal().getName();
-                final Bson filter = RequestFilters.buildGetFilter(agentId, Collections.singletonList(userName));
-
-                TimedRequest<FindIterable<Document>> timedRequest = new TimedRequest<>();
-
-                FindIterable<Document> documents = timedRequest.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
-                    @Override
-                    public FindIterable<Document> run() {
-                        return ThermostatMongoStorage.getDatabase().getCollection("agents").find(filter).sort(new BasicDBObject("_id", sortOrder)).limit(limit);
-                    }
-                });
-
-                asyncResponse.resume(Response.status(Response.Status.OK).entity(MongoResponseBuilder.buildJsonResponse(documents, timedRequest.getElapsed())).build());
-            }
-        }).start();
-    }
-
-    @Override
-    public Response putAgent(String body,
-                             @Context SecurityContext context) {
-        if (!ThermostatMongoStorage.isConnected()) {
-            return Response.status(Response.Status.OK).entity("PUT " + context.getUserPrincipal().getName() + "\n\n" + body).build();
-        }
-
-        TimedRequest<FindIterable<Document>> timedRequest = new TimedRequest<>();
-
-        /*
-         * TODO: Verify body matches expected schema
-         * TODO: Clean up insertion of tags into JSON body
-         */
-        final Document item = Document.parse(DocumentBuilder.addTags(body, context.getUserPrincipal().getName()));
-
-        timedRequest.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
-            @Override
-            public FindIterable<Document> run() {
-                ThermostatMongoStorage.getDatabase().getCollection("agents").insertOne(item);
-                return null;
-            }
-        });
-
-        return Response.status(Response.Status.OK).entity("PUT successful").build();
-    }
-
-    @Override
-    public Response getHostCpuInfo(SecurityContext securityContext,
-                                   String agentId,
-                                   String count,
-                                   String sort,
-                                   String maxTimestamp,
-                                   String minTimestamp) {
-        if (!ThermostatMongoStorage.isConnected()) {
-            return Response.status(Response.Status.OK).entity(agentId + count + sort + maxTimestamp + minTimestamp).build();
-        }
-
-        final int size = Integer.valueOf(count);
-
-        final String userName = securityContext.getUserPrincipal().getName();
-        final Bson filter = RequestFilters.buildGetFilter(agentId, Collections.singletonList(userName), maxTimestamp, minTimestamp);
-
-        final int sortOrder = Integer.valueOf(sort);
-
-        TimedRequest<FindIterable<Document>> request = new TimedRequest<>();
-        FindIterable<Document> documents = request.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
-            @Override
-            public FindIterable<Document> run() {
-                return ThermostatMongoStorage.getDatabase().getCollection("cpu-stats").find(filter).sort(new BasicDBObject("_id", sortOrder)).limit(size);
-            }
-        });
-
-        return Response.status(Response.Status.OK).entity(MongoResponseBuilder.buildJsonResponse(documents, request.getElapsed())).build();
-    }
-
-    @Override
-    public ChunkedOutput<String> streamHostCpuInfo(SecurityContext securityContext, String agentId) {
-        final ChunkedOutput<String> output = new ChunkedOutput<>(String.class, "\r\n");
-
-        new Thread() {
-            public void run() {
-                try {
-
-                    while (true) {
-                        TimedRequest<FindIterable<Document>> request = new TimedRequest<>();
-                        FindIterable<Document> documents = request.run(new TimedRequest.TimedRunnable<FindIterable<Document>>() {
-                            @Override
-                            public FindIterable<Document> run() {
-                                return ThermostatMongoStorage.getDatabase().getCollection("cpu-stats").find().sort(new BasicDBObject("_id", -1)).limit(1);
-                            }
-                        });
-                        output.write(MongoResponseBuilder.buildJsonResponse(documents, request.getElapsed()));
-
-                        Thread.sleep(1000L);
-                    }
-                } catch (IOException | InterruptedException e) {
-                    // An IOException occurs when reader closes the connection
-                } finally {
-                    try {
-                        output.close();
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
-                }
-            }
-        }.start();
-
-        return output;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/PluginStorageHandler.java	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,52 @@
+package com.redhat.thermostat.server.core.internal.web.handler.storage;
+
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.core.SecurityContext;
+
+import org.glassfish.jersey.server.ChunkedOutput;
+
+public interface PluginStorageHandler {
+    void getPath(SecurityContext securityContext,
+                 AsyncResponse asyncResponse,
+                 String path);
+
+    void putPath(SecurityContext securityContext,
+                 AsyncResponse asyncResponse,
+                 String path,
+                 String body);
+
+    void postPath(SecurityContext securityContext,
+                  AsyncResponse asyncResponse,
+                  String path,
+                  String body);
+
+    void deletePath(SecurityContext securityContext,
+                    AsyncResponse asyncResponse,
+                    String path);
+
+    ChunkedOutput<String> streamPath(SecurityContext securityContext,
+                                     AsyncResponse asyncResponse,
+                                     String path);
+
+    void getPathItem(SecurityContext securityContext,
+                     AsyncResponse asyncResponse,
+                     String path,
+                     String item);
+
+    void putPathItem(SecurityContext securityContext,
+                     AsyncResponse asyncResponse,
+                     String path,
+                     String item,
+                     String body);
+
+    void postPathItem(SecurityContext securityContext,
+                      AsyncResponse asyncResponse,
+                      String path,
+                      String item,
+                      String body);
+
+    void deletePathItem(SecurityContext securityContext,
+                        AsyncResponse asyncResponse,
+                        String path,
+                        String item);
+}
--- a/server/core/src/main/java/com/redhat/thermostat/server/core/internal/web/handler/storage/StorageHandler.java	Thu Feb 02 15:37:43 2017 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012-2017 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.server.core.internal.web.handler.storage;
-
-import javax.ws.rs.container.AsyncResponse;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
-
-import org.glassfish.jersey.server.ChunkedOutput;
-
-public interface StorageHandler {
-    void getAgent(SecurityContext securityContext,
-                  AsyncResponse asyncResponse,
-                  String agentId,
-                  String count,
-                  String sort);
-
-    Response putAgent(String body,
-                      SecurityContext context);
-
-    Response getHostCpuInfo(SecurityContext securityContext,
-                            String agentId,
-                            String count,
-                            String sort,
-                            String maxTimestamp,
-                            String minTimestamp);
-
-    ChunkedOutput<String> streamHostCpuInfo(SecurityContext securityContext,
-                                            String agentId);
-}
--- a/server/distribution/pom.xml	Thu Feb 02 15:37:43 2017 -0500
+++ b/server/distribution/pom.xml	Mon Feb 06 13:08:04 2017 -0500
@@ -57,17 +57,10 @@
         <plugins>
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
-                <dependencies>
-                    <dependency>
-                        <groupId>com.redhat.thermostat</groupId>
-                        <artifactId>thermostat-assembly</artifactId>
-                        <version>${project.version}</version>
-                    </dependency>
-                </dependencies>
                 <configuration>
-                    <descriptorRefs>
-                        <descriptorRef>plugin-assembly</descriptorRef>
-                    </descriptorRefs>
+                    <descriptors>
+                        <descriptor>src/assembly/src.xml</descriptor>
+                    </descriptors>
                     <appendAssemblyId>false</appendAssemblyId>
                 </configuration>
                 <executions>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/distribution/src/assembly/src.xml	Mon Feb 06 13:08:04 2017 -0500
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2017 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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+    <id>plugin-assembly</id>
+    <formats>
+        <format>zip</format>
+    </formats>
+    <baseDirectory>/</baseDirectory>
+    <includeBaseDirectory>true</includeBaseDirectory>
+
+
+    <dependencySets>
+        <dependencySet>
+            <useProjectArtifact>false</useProjectArtifact>
+            <useTransitiveDependencies>false</useTransitiveDependencies>
+            <outputDirectory>/plugins/${thermostat.plugin}</outputDirectory>
+        </dependencySet>
+    </dependencySets>
+    <files>
+        <file>
+            <source>thermostat-plugin.xml</source>
+            <outputDirectory>/plugins/${thermostat.plugin}</outputDirectory>
+            <filtered>true</filtered>
+        </file>
+    </files>
+</assembly>
+