killbill-aplcache

util, jaxrs: Add a new endpoint to retrieve all nodes info (component

11/13/2015 5:28:11 PM

Details

diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NodeInfoJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NodeInfoJson.java
new file mode 100644
index 0000000..5da94b8
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/NodeInfoJson.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class NodeInfoJson {
+
+    private final String kbVersion;
+    private final String apiVersion;
+    private final String pluginApiVersion;
+    private final String commonVersion;
+    private final String platformVersion;
+    private final List<PluginInfoJson> pluginsInfo;
+
+    @JsonCreator
+    public NodeInfoJson(@JsonProperty("kbVersion") final String kbVersion,
+                        @JsonProperty("apiVersion") final String apiVersion,
+                        @JsonProperty("pluginApiVersion") final String pluginApiVersion,
+                        @JsonProperty("commonVersion") final String commonVersion,
+                        @JsonProperty("platformVersion") final String platformVersion,
+                        @JsonProperty("pluginsInfo") final List<PluginInfoJson> pluginsInfo) {
+        this.kbVersion = kbVersion;
+        this.apiVersion = apiVersion;
+        this.pluginApiVersion = pluginApiVersion;
+        this.commonVersion = commonVersion;
+        this.platformVersion = platformVersion;
+        this.pluginsInfo = pluginsInfo;
+    }
+
+    public String getKbVersion() {
+        return kbVersion;
+    }
+
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    public String getPluginApiVersion() {
+        return pluginApiVersion;
+    }
+
+    public String getCommonVersion() {
+        return commonVersion;
+    }
+
+    public String getPlatformVersion() {
+        return platformVersion;
+    }
+
+    public List<PluginInfoJson> getPluginsInfo() {
+        return pluginsInfo;
+    }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
index 518b402..85234aa 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxrsResource.java
@@ -205,6 +205,9 @@ public interface JaxrsResource {
     public static final String PLUGINS_INFO = "pluginsInfo";
     public static final String PLUGINS_INFO_PATH = PREFIX + "/" + PLUGINS_INFO;
 
+    public static final String NODES_INFO = "nodesInfo";
+    public static final String NODES_INFO_PATH = PREFIX + "/" + NODES_INFO;
+
     // No PREFIX here!
     public static final String PLUGINS = "plugins";
     public static final String PLUGINS_PATH = "/" + PLUGINS;
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java
new file mode 100644
index 0000000..96913da
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/NodesInfoResource.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.jaxrs.resources;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.killbill.billing.account.api.AccountUserApi;
+import org.killbill.billing.entitlement.api.SubscriptionApiException;
+import org.killbill.billing.jaxrs.json.NodeInfoJson;
+import org.killbill.billing.jaxrs.json.PluginInfoJson;
+import org.killbill.billing.jaxrs.json.PluginInfoJson.PluginServiceInfoJson;
+import org.killbill.billing.jaxrs.util.Context;
+import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.osgi.api.PluginInfo;
+import org.killbill.billing.osgi.api.PluginServiceInfo;
+import org.killbill.billing.payment.api.PaymentApi;
+import org.killbill.billing.util.api.AuditUserApi;
+import org.killbill.billing.util.api.CustomFieldUserApi;
+import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.info.KillbillInfoApi;
+import org.killbill.billing.util.info.NodeInfo;
+import org.killbill.clock.Clock;
+import org.killbill.commons.metrics.TimedResource;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+@Path(JaxrsResource.NODES_INFO_PATH)
+@Api(value = JaxrsResource.NODES_INFO_PATH, description = "Operations to retrieve nodes info")
+public class NodesInfoResource extends JaxRsResourceBase {
+
+    private final KillbillInfoApi killbillInfoApi;
+
+    @Inject
+    public NodesInfoResource(final JaxrsUriBuilder uriBuilder,
+                             final TagUserApi tagUserApi,
+                             final CustomFieldUserApi customFieldUserApi,
+                             final AuditUserApi auditUserApi,
+                             final AccountUserApi accountUserApi,
+                             final PaymentApi paymentApi,
+                             final KillbillInfoApi killbillInfoApi,
+                             final Clock clock,
+                             final Context context) {
+        super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, accountUserApi, paymentApi, clock, context);
+        this.killbillInfoApi = killbillInfoApi;
+    }
+
+    @TimedResource
+    @GET
+    @Produces(APPLICATION_JSON)
+    @ApiOperation(value = "Retrieve all the nodes infos", response = PluginInfoJson.class, responseContainer = "List")
+    public Response getNodesInfo(@javax.ws.rs.core.Context final HttpServletRequest request) throws SubscriptionApiException {
+
+        final Iterable<NodeInfo> nodeInfos = killbillInfoApi.getNodesInfo();
+
+        final List<NodeInfoJson> nodeInfosJson = ImmutableList.copyOf(Iterables.transform(nodeInfos, new Function<NodeInfo, NodeInfoJson>() {
+            @Override
+            public NodeInfoJson apply(final NodeInfo input) {
+                final Iterable<PluginInfo> pluginsInfo = input.getPluginInfo();
+
+                final List<PluginInfoJson> pluginsInfoJson = ImmutableList.copyOf(Iterables.transform(pluginsInfo, new Function<PluginInfo, PluginInfoJson>() {
+                    @Override
+                    public PluginInfoJson apply(final PluginInfo input) {
+
+                        final Set<PluginServiceInfo> services = input.getServices();
+                        final Set<PluginServiceInfoJson> servicesJson = ImmutableSet.copyOf(Iterables.transform(services, new Function<PluginServiceInfo, PluginServiceInfoJson>() {
+
+                            @Override
+                            public PluginServiceInfoJson apply(final PluginServiceInfo input) {
+                                return new PluginServiceInfoJson(input.getServiceTypeName(), input.getRegistrationName());
+                            }
+                        }));
+                        return new PluginInfoJson(input.getBundleSymbolicName(),
+                                                  input.getPluginName(),
+                                                  input.getVersion(),
+                                                  input.isRunning(),
+                                                  servicesJson);
+                    }
+                }));
+
+                return new NodeInfoJson(input.getKillbillVersion(),
+                                        input.getApiVersion(),
+                                        input.getPluginApiVersion(),
+                                        input.getCommonVersion(),
+                                        input.getPlatformVersion(),
+                                        pluginsInfoJson);
+            }
+        }));
+
+        return Response.status(Status.OK).entity(nodeInfosJson).build();
+    }
+
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
index ade5a6e..bb85d35 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillbillServerModule.java
@@ -36,6 +36,7 @@ import org.killbill.billing.jaxrs.resources.CustomFieldResource;
 import org.killbill.billing.jaxrs.resources.ExportResource;
 import org.killbill.billing.jaxrs.resources.InvoicePaymentResource;
 import org.killbill.billing.jaxrs.resources.InvoiceResource;
+import org.killbill.billing.jaxrs.resources.NodesInfoResource;
 import org.killbill.billing.jaxrs.resources.PaymentGatewayResource;
 import org.killbill.billing.jaxrs.resources.PaymentMethodResource;
 import org.killbill.billing.jaxrs.resources.PaymentResource;
@@ -192,6 +193,7 @@ public class KillbillServerModule extends KillbillPlatformModule {
         bind(UsageResource.class).asEagerSingleton();
         bind(AdminResource.class).asEagerSingleton();
         bind(PluginInfoResource.class).asEagerSingleton();
+        bind(NodesInfoResource.class).asEagerSingleton();
     }
 
     protected void configureFilters() {

util/pom.xml 4(+4 -0)

diff --git a/util/pom.xml b/util/pom.xml
index e6d18a8..9227cf8 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -137,6 +137,10 @@
         </dependency>
         <dependency>
             <groupId>org.kill-bill.billing</groupId>
+            <artifactId>killbill-platform-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.kill-bill.billing</groupId>
             <artifactId>killbill-platform-osgi-api</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/glue/InfoModule.java b/util/src/main/java/org/killbill/billing/util/glue/InfoModule.java
index f826d6d..0d8248c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/InfoModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/InfoModule.java
@@ -22,6 +22,7 @@ import org.killbill.billing.util.info.DefaultKillbillInfoApi;
 import org.killbill.billing.util.info.DefaultKillbillInfoService;
 import org.killbill.billing.util.info.KillbillInfoApi;
 import org.killbill.billing.util.info.KillbillInfoService;
+import org.killbill.billing.util.info.NodeInfoMapper;
 import org.killbill.billing.util.info.dao.DefaultNodeInfoDao;
 import org.killbill.billing.util.info.dao.NodeInfoDao;
 
@@ -38,6 +39,7 @@ public class InfoModule extends KillBillModule {
     protected void installUserApi() {
         bind(KillbillInfoApi.class).to(DefaultKillbillInfoApi.class).asEagerSingleton();
         bind(KillbillInfoService.class).to(DefaultKillbillInfoService.class).asEagerSingleton();
+        bind(NodeInfoMapper.class).asEagerSingleton();
     }
 
 
diff --git a/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoApi.java b/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoApi.java
index 6b9de08..0ecc3c8 100644
--- a/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoApi.java
+++ b/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoApi.java
@@ -17,17 +17,54 @@
 
 package org.killbill.billing.util.info;
 
+import java.io.IOException;
+import java.util.List;
+
 import org.killbill.billing.osgi.api.PluginInfo;
+import org.killbill.billing.util.info.dao.NodeInfoDao;
+import org.killbill.billing.util.info.dao.NodeInfoModelDao;
+import org.killbill.billing.util.info.json.NodeInfoModelJson;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
 
 public class DefaultKillbillInfoApi implements KillbillInfoApi {
 
+    private final NodeInfoDao nodeInfoDao;
+    private final NodeInfoMapper mapper;
+
+    @Inject
+    public DefaultKillbillInfoApi(final NodeInfoDao nodeInfoDao, final NodeInfoMapper mapper) {
+        this.nodeInfoDao = nodeInfoDao;
+        this.mapper = mapper;
+    }
+
     @Override
-    public Iterable<NodeInfo> getNodeInfo() {
-        return null;
+    public Iterable<NodeInfo> getNodesInfo() {
+        final List<NodeInfoModelDao> allNodes = nodeInfoDao.getAll();
+
+        final Iterable<NodeInfoModelJson> allModelNodes = Iterables.transform(allNodes, new Function<NodeInfoModelDao, NodeInfoModelJson>() {
+            @Override
+            public NodeInfoModelJson apply(final NodeInfoModelDao input) {
+                try {
+                    return mapper.deserialize(input.getNodeInfo());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+
+        return Iterables.transform(allModelNodes, new Function<NodeInfoModelJson, NodeInfo>() {
+            @Override
+            public NodeInfo apply(final NodeInfoModelJson input) {
+                return new DefaultNodeInfo(input);
+            }
+        });
     }
 
     @Override
-    public void updatePluginInfo(final Iterable<PluginInfo> iterable) {
+    public void updatePluginInfo(final Iterable<PluginInfo> plugins) {
 
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoService.java b/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoService.java
index 2f312de..d12777d 100644
--- a/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoService.java
+++ b/util/src/main/java/org/killbill/billing/util/info/DefaultKillbillInfoService.java
@@ -29,15 +29,16 @@ import org.killbill.billing.platform.api.LifecycleHandlerType;
 import org.killbill.billing.platform.api.LifecycleHandlerType.LifecycleLevel;
 import org.killbill.billing.util.info.dao.NodeInfoDao;
 import org.killbill.billing.util.info.dao.NodeInfoModelDao;
+import org.killbill.billing.util.info.json.NodeInfoModelJson;
+import org.killbill.billing.util.info.json.PluginInfoModelJson;
 import org.killbill.clock.Clock;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public class DefaultKillbillInfoService implements KillbillInfoService {
 
@@ -45,21 +46,17 @@ public class DefaultKillbillInfoService implements KillbillInfoService {
 
     public static final String INFO_SERVICE_NAME = "info-service";
 
-    private final ObjectMapper mapper;
-
     private final NodeInfoDao nodeInfoDao;
     private final PluginsInfoApi pluginInfoApi;
     private final Clock clock;
+    private final NodeInfoMapper mapper;
 
     @Inject
-    public DefaultKillbillInfoService(final NodeInfoDao nodeInfoDao, final PluginsInfoApi pluginInfoApi, final Clock clock) {
+    public DefaultKillbillInfoService(final NodeInfoDao nodeInfoDao, final PluginsInfoApi pluginInfoApi, final Clock clock, final NodeInfoMapper mapper) {
         this.nodeInfoDao = nodeInfoDao;
         this.pluginInfoApi = pluginInfoApi;
         this.clock = clock;
-        this.mapper = new ObjectMapper();
-        mapper.registerModule(new JodaModule());
-        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
-
+        this.mapper = mapper;
     }
 
     @Override
@@ -91,8 +88,17 @@ public class DefaultKillbillInfoService implements KillbillInfoService {
         final String kbPluginApiVersion  = org.killbill.billing.util.info.KillbillVersions.getPluginApiVersion();
         final String kbPlatformVersion  = org.killbill.billing.util.info.KillbillVersions.getPlatformVersion();
         final String kbCommonVersion  = org.killbill.billing.util.info.KillbillVersions.getCommonVersion();
-        final NodeInfo nodeInfo = new DefaultNodeInfo(CreatorName.get(), bootTime, bootTime, kbVersion, kbApiVersion, kbPluginApiVersion, kbCommonVersion, kbPlatformVersion, pluginInfo);
-        final String nodeInfoValue = mapper.writeValueAsString(nodeInfo);
+
+
+        final NodeInfoModelJson nodeInfo = new NodeInfoModelJson(CreatorName.get(), bootTime, bootTime, kbVersion, kbApiVersion, kbPluginApiVersion, kbCommonVersion, kbPlatformVersion,
+             ImmutableList.copyOf(Iterables.transform(pluginInfo, new Function<PluginInfo, PluginInfoModelJson>() {
+                 @Override
+                 public PluginInfoModelJson apply(final PluginInfo input) {
+                     return new PluginInfoModelJson(input);
+                 }
+             })));
+
+        final String nodeInfoValue = mapper.serialize(nodeInfo);
         final NodeInfoModelDao bootNodeInfo = new NodeInfoModelDao(CreatorName.get(), clock.getUTCNow(), nodeInfoValue);
         nodeInfoDao.create(bootNodeInfo);
     }
diff --git a/util/src/main/java/org/killbill/billing/util/info/DefaultNodeInfo.java b/util/src/main/java/org/killbill/billing/util/info/DefaultNodeInfo.java
index 3b6c3cf..a30dc26 100644
--- a/util/src/main/java/org/killbill/billing/util/info/DefaultNodeInfo.java
+++ b/util/src/main/java/org/killbill/billing/util/info/DefaultNodeInfo.java
@@ -17,41 +17,91 @@
 
 package org.killbill.billing.util.info;
 
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
+import org.killbill.billing.osgi.api.DefaultPluginsInfoApi.DefaultPluginInfo;
+import org.killbill.billing.osgi.api.DefaultPluginsInfoApi.DefaultPluginServiceInfo;
 import org.killbill.billing.osgi.api.PluginInfo;
+import org.killbill.billing.osgi.api.PluginServiceInfo;
+import org.killbill.billing.util.info.dao.NodeInfoModelDao;
+import org.killbill.billing.util.info.json.NodeInfoModelJson;
+import org.killbill.billing.util.info.json.PluginInfoModelJson;
+import org.killbill.billing.util.info.json.PluginServiceInfoModelJson;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 
 public class DefaultNodeInfo implements NodeInfo {
 
     private final String nodeName;
     private final DateTime bootTime;
-    private final DateTime updatedDate;
+    private final DateTime lastUpdatedDate;
     private final String killbillVersion;
     private final String apiVersion;
-    private final String pluginApiVersion;
-    private final String commonVersion;
     private final String platformVersion;
+    private final String commonVersion;
+    private final String pluginApiVersion;
     private final Iterable<PluginInfo> pluginInfo;
 
     public DefaultNodeInfo(final String nodeName,
                            final DateTime bootTime,
-                           final DateTime updatedDate,
+                           final DateTime lastUpdatedDate,
                            final String killbillVersion,
                            final String apiVersion,
-                           final String pluginApiVersion,
-                           final String commonVersion,
                            final String platformVersion,
+                           final String commonVersion,
+                           final String pluginApiVersion,
                            final Iterable<PluginInfo> pluginInfo) {
         this.nodeName = nodeName;
         this.bootTime = bootTime;
-        this.updatedDate = updatedDate;
+        this.lastUpdatedDate = lastUpdatedDate;
         this.killbillVersion = killbillVersion;
         this.apiVersion = apiVersion;
-        this.pluginApiVersion = pluginApiVersion;
-        this.commonVersion = commonVersion;
         this.platformVersion = platformVersion;
+        this.commonVersion = commonVersion;
+        this.pluginApiVersion = pluginApiVersion;
         this.pluginInfo = pluginInfo;
     }
 
+    public DefaultNodeInfo(final NodeInfoModelJson in) {
+        this(in.getNodeName(),
+             in.getBootTime(),
+             in.getBootTime(),
+             in.getKillbillVersion(),
+             in.getApiVersion(),
+             in.getPlatformVersion(),
+             in.getCommonVersion(),
+             in.getPluginApiVersion(),
+             toPluginInfo(in.getPluginInfo()));
+    }
+
+    private static Set<PluginServiceInfo> toPluginServiceInfo(final Set<PluginServiceInfoModelJson> services) {
+        return ImmutableSet.<PluginServiceInfo>copyOf(Iterables.transform(services, new Function<PluginServiceInfoModelJson, PluginServiceInfo>() {
+
+            @Nullable
+            @Override
+            public PluginServiceInfo apply(final PluginServiceInfoModelJson input) {
+                return new DefaultPluginServiceInfo(input.getServiceTypeName(), input.getRegistrationName());
+            }
+        }));
+    }
+
+    private static Iterable<PluginInfo> toPluginInfo(final Iterable<PluginInfoModelJson> plugins) {
+        return Iterables.transform(plugins, new Function<PluginInfoModelJson, PluginInfo>() {
+            @Override
+            public PluginInfo apply(final PluginInfoModelJson input) {
+
+                return new DefaultPluginInfo(input.getBundleSymbolicName(), input.getPluginName(), input.getVersion(), input.isRunning(), toPluginServiceInfo(input.getServices()));
+            }
+        });
+    }
+
     @Override
     public String getNodeName() {
         return nodeName;
@@ -64,7 +114,7 @@ public class DefaultNodeInfo implements NodeInfo {
 
     @Override
     public DateTime getLastUpdatedDate() {
-        return updatedDate;
+        return lastUpdatedDate;
     }
 
     @Override
diff --git a/util/src/main/java/org/killbill/billing/util/info/json/NodeInfoModelJson.java b/util/src/main/java/org/killbill/billing/util/info/json/NodeInfoModelJson.java
new file mode 100644
index 0000000..af9b3e5
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/info/json/NodeInfoModelJson.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.info.json;
+
+import java.util.List;
+
+import org.joda.time.DateTime;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class NodeInfoModelJson {
+
+    private String nodeName;
+    private DateTime bootTime;
+    private DateTime updatedDate;
+    private String killbillVersion;
+    private String apiVersion;
+    private String pluginApiVersion;
+    private String commonVersion;
+    private String platformVersion;
+    private List<PluginInfoModelJson> pluginInfo;
+
+    @JsonCreator
+    public NodeInfoModelJson(@JsonProperty("nodeName") final String nodeName,
+                             @JsonProperty("bootTime") final DateTime bootTime,
+                             @JsonProperty("updatedDate") final DateTime updatedDate,
+                             @JsonProperty("killbillVersion") final String killbillVersion,
+                             @JsonProperty("apiVersion") final String apiVersion,
+                             @JsonProperty("pluginApiVersion") final String pluginApiVersion,
+                             @JsonProperty("commonVersion") final String commonVersion,
+                             @JsonProperty("platformVersion") final String platformVersion,
+                             @JsonProperty("pluginInfo") final List<PluginInfoModelJson> pluginInfo) {
+        this.nodeName = nodeName;
+        this.bootTime = bootTime;
+        this.updatedDate = updatedDate;
+        this.killbillVersion = killbillVersion;
+        this.apiVersion = apiVersion;
+        this.pluginApiVersion = pluginApiVersion;
+        this.commonVersion = commonVersion;
+        this.platformVersion = platformVersion;
+        this.pluginInfo = pluginInfo;
+    }
+
+    public String getNodeName() {
+        return nodeName;
+    }
+
+    public DateTime getBootTime() {
+        return bootTime;
+    }
+
+    public String getKillbillVersion() {
+        return killbillVersion;
+    }
+
+    public String getApiVersion() {
+        return apiVersion;
+    }
+
+    public String getPlatformVersion() {
+        return platformVersion;
+    }
+
+    public String getCommonVersion() {
+        return commonVersion;
+    }
+
+    public String getPluginApiVersion() {
+        return pluginApiVersion;
+    }
+
+    public Iterable<PluginInfoModelJson> getPluginInfo() {
+        return pluginInfo;
+    }
+
+    public DateTime getUpdatedDate() {
+        return updatedDate;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof NodeInfoModelJson)) {
+            return false;
+        }
+
+        final NodeInfoModelJson that = (NodeInfoModelJson) o;
+
+        if (nodeName != null ? !nodeName.equals(that.nodeName) : that.nodeName != null) {
+            return false;
+        }
+        if (bootTime != null ? bootTime.compareTo(that.bootTime) != 0 : that.bootTime != null) {
+            return false;
+        }
+        if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
+            return false;
+        }
+        if (killbillVersion != null ? !killbillVersion.equals(that.killbillVersion) : that.killbillVersion != null) {
+            return false;
+        }
+        if (apiVersion != null ? !apiVersion.equals(that.apiVersion) : that.apiVersion != null) {
+            return false;
+        }
+        if (pluginApiVersion != null ? !pluginApiVersion.equals(that.pluginApiVersion) : that.pluginApiVersion != null) {
+            return false;
+        }
+        if (commonVersion != null ? !commonVersion.equals(that.commonVersion) : that.commonVersion != null) {
+            return false;
+        }
+        if (platformVersion != null ? !platformVersion.equals(that.platformVersion) : that.platformVersion != null) {
+            return false;
+        }
+        return !(pluginInfo != null ? !pluginInfo.equals(that.pluginInfo) : that.pluginInfo != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = nodeName != null ? nodeName.hashCode() : 0;
+        result = 31 * result + (bootTime != null ? bootTime.hashCode() : 0);
+        result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+        result = 31 * result + (killbillVersion != null ? killbillVersion.hashCode() : 0);
+        result = 31 * result + (apiVersion != null ? apiVersion.hashCode() : 0);
+        result = 31 * result + (pluginApiVersion != null ? pluginApiVersion.hashCode() : 0);
+        result = 31 * result + (commonVersion != null ? commonVersion.hashCode() : 0);
+        result = 31 * result + (platformVersion != null ? platformVersion.hashCode() : 0);
+        result = 31 * result + (pluginInfo != null ? pluginInfo.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/info/json/PluginInfoModelJson.java b/util/src/main/java/org/killbill/billing/util/info/json/PluginInfoModelJson.java
new file mode 100644
index 0000000..a376ef6
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/info/json/PluginInfoModelJson.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.info.json;
+
+import java.util.Set;
+
+import org.killbill.billing.osgi.api.PluginInfo;
+import org.killbill.billing.osgi.api.PluginServiceInfo;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class PluginInfoModelJson {
+
+    private final String bundleSymbolicName;
+
+    private final String pluginName;
+
+    private final String version;
+
+    private final boolean running;
+
+    private final Set<PluginServiceInfoModelJson> services;
+
+    @JsonCreator
+    public PluginInfoModelJson(@JsonProperty("bundleSymbolicName") final String bundleSymbolicName,
+                               @JsonProperty("pluginName") final String pluginName,
+                               @JsonProperty("version") final String version,
+                               @JsonProperty("running") final boolean running,
+                               @JsonProperty("services") final Set<PluginServiceInfoModelJson> services) {
+        this.bundleSymbolicName = bundleSymbolicName;
+        this.pluginName = pluginName;
+        this.version = version;
+        this.running = running;
+        this.services = services;
+    }
+
+    public PluginInfoModelJson(final PluginInfo input) {
+        this(input.getBundleSymbolicName(),
+             input.getPluginName(),
+             input.getVersion(),
+             input.isRunning(),
+             ImmutableSet.copyOf(Iterables.transform(input.getServices(), new Function<PluginServiceInfo, PluginServiceInfoModelJson>() {
+                 @Override
+                 public PluginServiceInfoModelJson apply(final PluginServiceInfo input) {
+                     return new PluginServiceInfoModelJson(input.getServiceTypeName(), input.getRegistrationName());
+                 }
+             })));
+    }
+
+    public String getBundleSymbolicName() {
+        return bundleSymbolicName;
+    }
+
+    public String getPluginName() {
+        return pluginName;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean isRunning() {
+        return running;
+    }
+
+    public Set<PluginServiceInfoModelJson> getServices() {
+        return services;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PluginInfoModelJson)) {
+            return false;
+        }
+
+        final PluginInfoModelJson that = (PluginInfoModelJson) o;
+
+        if (running != that.running) {
+            return false;
+        }
+        if (bundleSymbolicName != null ? !bundleSymbolicName.equals(that.bundleSymbolicName) : that.bundleSymbolicName != null) {
+            return false;
+        }
+        if (pluginName != null ? !pluginName.equals(that.pluginName) : that.pluginName != null) {
+            return false;
+        }
+        if (version != null ? !version.equals(that.version) : that.version != null) {
+            return false;
+        }
+        return !(services != null ? !services.equals(that.services) : that.services != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = bundleSymbolicName != null ? bundleSymbolicName.hashCode() : 0;
+        result = 31 * result + (pluginName != null ? pluginName.hashCode() : 0);
+        result = 31 * result + (version != null ? version.hashCode() : 0);
+        result = 31 * result + (running ? 1 : 0);
+        result = 31 * result + (services != null ? services.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/info/json/PluginServiceInfoModelJson.java b/util/src/main/java/org/killbill/billing/util/info/json/PluginServiceInfoModelJson.java
new file mode 100644
index 0000000..91f1fb9
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/info/json/PluginServiceInfoModelJson.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.info.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PluginServiceInfoModelJson {
+
+    private final String serviceTypeName;
+    private final String registrationName;
+
+    @JsonCreator
+    public PluginServiceInfoModelJson(@JsonProperty("serviceTypeName") final String serviceTypeName,
+                                      @JsonProperty("registrationName") final String registrationName) {
+        this.serviceTypeName = serviceTypeName;
+        this.registrationName = registrationName;
+    }
+
+    public String getServiceTypeName() {
+        return serviceTypeName;
+    }
+
+    public String getRegistrationName() {
+        return registrationName;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PluginServiceInfoModelJson)) {
+            return false;
+        }
+
+        final PluginServiceInfoModelJson that = (PluginServiceInfoModelJson) o;
+
+        if (serviceTypeName != null ? !serviceTypeName.equals(that.serviceTypeName) : that.serviceTypeName != null) {
+            return false;
+        }
+        return !(registrationName != null ? !registrationName.equals(that.registrationName) : that.registrationName != null);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = serviceTypeName != null ? serviceTypeName.hashCode() : 0;
+        result = 31 * result + (registrationName != null ? registrationName.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/info/NodeInfoMapper.java b/util/src/main/java/org/killbill/billing/util/info/NodeInfoMapper.java
new file mode 100644
index 0000000..f4c6e49
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/info/NodeInfoMapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.info;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.util.info.json.NodeInfoModelJson;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+
+public class NodeInfoMapper {
+
+    private final ObjectMapper mapper;
+
+    @Inject
+    public NodeInfoMapper() {
+        this.mapper = new ObjectMapper();
+        mapper.registerModule(new JodaModule());
+        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+    }
+
+    public String serialize(final NodeInfoModelJson nodeInfo) throws JsonProcessingException {
+        return mapper.writeValueAsString(nodeInfo);
+    }
+
+
+    public NodeInfoModelJson deserialize(final String nodeInfo) throws IOException {
+        return mapper.readValue(nodeInfo, NodeInfoModelJson.class);
+    }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/info/TestNodeInfoMapper.java b/util/src/test/java/org/killbill/billing/util/info/TestNodeInfoMapper.java
new file mode 100644
index 0000000..0f4ef3d
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/info/TestNodeInfoMapper.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.util.info;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.killbill.billing.osgi.api.PluginInfo;
+import org.killbill.billing.osgi.api.PluginServiceInfo;
+import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.killbill.billing.util.info.json.NodeInfoModelJson;
+import org.killbill.billing.util.info.json.PluginInfoModelJson;
+import org.killbill.billing.util.info.json.PluginServiceInfoModelJson;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+
+public class TestNodeInfoMapper extends UtilTestSuiteNoDB {
+
+    @Inject
+    protected NodeInfoMapper nodeInfoMapper;
+
+
+    @Test(groups = "fast")
+    public void testBasic() throws Exception {
+
+
+        final PluginServiceInfoModelJson svc = new PluginServiceInfoModelJson("typeName", "registrationName");
+
+        final Set<PluginServiceInfoModelJson> services1 = new HashSet<PluginServiceInfoModelJson>();
+        services1.add(svc);
+
+        final List<PluginInfoModelJson> pluginInfos = new ArrayList<PluginInfoModelJson>();
+        final PluginInfoModelJson info1 = new PluginInfoModelJson("sym1", "name1", "vers1", true, services1);
+        pluginInfos.add(info1);
+        final NodeInfoModelJson input = new NodeInfoModelJson("nodeName", clock.getUTCNow(), clock.getUTCNow(), "1.0", "1.0", "1.0", "1.0", "1.0", pluginInfos);
+
+        final String nodeInfoStr = nodeInfoMapper.serialize(input);
+
+
+        final NodeInfoModelJson res = nodeInfoMapper.deserialize(nodeInfoStr);
+
+        Assert.assertEquals(res, input);
+    }
+
+}