killbill-aplcache

Ongoing work on jaxrs resources

4/10/2012 5:11:44 PM

Changes

bin/db-helper 2(+1 -1)

bin/start-server 5(+3 -2)

pom.xml 7(+7 -0)

server/pom.xml 6(+6 -0)

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
index 11a5a39..33e1e3a 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountChangeNotification.java
@@ -102,4 +102,9 @@ public class DefaultAccountChangeNotification implements AccountChangeNotificati
             inputList.add(new DefaultChangedField(key, oldData, newData));
         }
     }
+
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.ACCOUNT_CHANGE;
+	}
 }
diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
index 91d9b82..19b76e5 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountCreationEvent.java
@@ -19,6 +19,7 @@ package com.ning.billing.account.api.user;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountCreationNotification;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
 
 import java.util.UUID;
 
@@ -40,4 +41,10 @@ public class DefaultAccountCreationEvent implements AccountCreationNotification 
     public AccountData getData() {
         return data;
     }
+    
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.ACCOUNT_CREATE;
+	}
+
 }
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
index 495e985..99c68d6 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/TestAnalyticsService.java
@@ -190,8 +190,7 @@ public class TestAnalyticsService {
         helper.initDb(paymentDdl);
         helper.initDb(utilDdl);
 
-        helper.cleanupTable("tag_definitions");
-        helper.cleanupTable("accounts");
+    	helper.cleanupAllTables();
     }
 
     private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException {
@@ -289,6 +288,7 @@ public class TestAnalyticsService {
 
         // Send events and wait for the async part...
         bus.post(transition);
+
         bus.post(accountCreationNotification);
         Thread.sleep(1000);
 
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
index 5541b01..d546e3f 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentError.java
@@ -17,10 +17,12 @@
 package com.ning.billing.payment.api;
 import java.util.UUID;
 
+import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonTypeInfo;
 import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
 
 import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
 
 @JsonTypeInfo(use = Id.NAME, property = "error")
 public class PaymentError implements BusEvent {
@@ -117,5 +119,12 @@ public class PaymentError implements BusEvent {
     public String toString() {
         return "PaymentError [type=" + type + ", message=" + message + ", accountId=" + accountId + ", invoiceId=" + invoiceId + "]";
     }
+    
+    @JsonIgnore
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.PAYMENT_ERROR;
+	}
+
 
 }
diff --git a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
index e325d9a..ad0c516 100644
--- a/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
+++ b/api/src/main/java/com/ning/billing/payment/api/PaymentInfo.java
@@ -19,12 +19,14 @@ package com.ning.billing.payment.api;
 import java.math.BigDecimal;
 
 import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 
 import com.google.common.base.Objects;
 import com.ning.billing.util.bus.BusEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
 
 public class PaymentInfo implements BusEvent {
     private final String paymentId;
@@ -348,4 +350,11 @@ public class PaymentInfo implements BusEvent {
     private static long getUnixTimestamp(final DateTime dateTime) {
         return dateTime.getMillis() / 1000;
     }
+    
+    @JsonIgnore
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.PAYMENT_INFO;
+	}
+
 }
diff --git a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
index 481cb74..ef5bc04 100644
--- a/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
+++ b/api/src/main/java/com/ning/billing/util/bus/BusEvent.java
@@ -17,5 +17,16 @@
 package com.ning.billing.util.bus;
 
 public interface BusEvent {
+	
+	public enum BusEventType {
+		ACCOUNT_CREATE,
+		ACCOUNT_CHANGE,
+		SUBSCRIPTION_TRANSITION,
+		INVOICE_CREATION,
+		PAYMENT_INFO,
+		PAYMENT_ERROR
+	}
+
+	public BusEventType getBusEventType();
 
 }

bin/db-helper 2(+1 -1)

diff --git a/bin/db-helper b/bin/db-helper
index 1d4a5b9..3bd86ae 100755
--- a/bin/db-helper
+++ b/bin/db-helper
@@ -37,7 +37,7 @@ CLEAN_FILE=
 
 function usage() {
     echo -n "./db_helper "
-    echo -n " -a <create|clean>"
+    echo -n " -a <create|clean|dump>"
     echo -n " -d database_name (default = killbill)"    
     echo -n " -u user_name (default = root)"
     echo -n " -p password (default = root)"

bin/start-server 5(+3 -2)

diff --git a/bin/start-server b/bin/start-server
index bc60df8..ce254da 100755
--- a/bin/start-server
+++ b/bin/start-server
@@ -93,6 +93,7 @@ while getopts ":pswdh" options; do
 done
 
 if [ ! -z $START ]; then
-    echo "coucouc"
     start
-fi
\ No newline at end of file
+else
+    usage
+fi
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
index f03193b..18a1471 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionTransitionData.java
@@ -22,6 +22,8 @@ import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.user.ApiEventType;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
 import org.joda.time.DateTime;
 
 import java.util.UUID;
@@ -186,4 +188,8 @@ public class SubscriptionTransitionData implements SubscriptionTransition {
             + ", nextPhase=" + ((nextPhase != null) ? nextPhase.getName() : null) + "]";
     }
 
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.SUBSCRIPTION_TRANSITION;
+	}
 }
diff --git a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
index 5c6785c..8156779 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/api/user/DefaultInvoiceCreationNotification.java
@@ -23,6 +23,7 @@ import org.joda.time.DateTime;
 
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.invoice.api.InvoiceCreationNotification;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
 
 public class DefaultInvoiceCreationNotification implements InvoiceCreationNotification {
     private final UUID invoiceId;
@@ -68,5 +69,11 @@ public class DefaultInvoiceCreationNotification implements InvoiceCreationNotifi
     public String toString() {
         return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
     }
+    
+    
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.INVOICE_CREATION;
+	}
 
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJson.java
index 226c365..12543cc 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/BundleJson.java
@@ -22,6 +22,8 @@ import org.codehaus.jackson.annotate.JsonCreator;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonView;
 
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+
 public class BundleJson {
 
     @JsonView(BundleTimelineViews.Base.class)
@@ -63,4 +65,75 @@ public class BundleJson {
     public List<SubscriptionJson> getSubscriptions() {
         return subscriptions;
     }
+    
+    public BundleJson(SubscriptionBundle bundle) {
+        this.bundleId = bundle.getId().toString();
+        this.accountId = bundle.getAccountId().toString();
+        this.externalKey = bundle.getKey();
+        this.subscriptions = null;
+    }
+    
+    public BundleJson() {
+        this.bundleId = null;
+        this.accountId = null;
+        this.externalKey = null;
+        this.subscriptions = null;
+    }
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((accountId == null) ? 0 : accountId.hashCode());
+		result = prime * result
+				+ ((bundleId == null) ? 0 : bundleId.hashCode());
+		result = prime * result
+				+ ((externalKey == null) ? 0 : externalKey.hashCode());
+		result = prime * result
+				+ ((subscriptions == null) ? 0 : subscriptions.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (equalsNoId(obj) == false) {
+			return false;
+		}
+		BundleJson other = (BundleJson) obj;
+		if (bundleId == null) {
+			if (other.bundleId != null)
+				return false;
+		} else if (!bundleId.equals(other.bundleId))
+			return false;
+		return true;
+	}
+
+	public boolean equalsNoId(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		BundleJson other = (BundleJson) obj;
+		if (accountId == null) {
+			if (other.accountId != null)
+				return false;
+		} else if (!accountId.equals(other.accountId))
+			return false;
+		if (externalKey == null) {
+			if (other.externalKey != null)
+				return false;
+		} else if (!externalKey.equals(other.externalKey))
+			return false;
+		if (subscriptions == null) {
+			if (other.subscriptions != null)
+				return false;
+		} else if (!subscriptions.equals(other.subscriptions))
+			return false;
+		return true;
+	}
+    
+    
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJson.java
index a08a305..20b222c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJson.java
@@ -23,6 +23,8 @@ import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonView;
 import org.joda.time.DateTime;
 
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.util.clock.DefaultClock;
 
 public class SubscriptionJson {
@@ -223,6 +225,31 @@ public class SubscriptionJson {
         this.deletedEvents = deletedEvents;
         this.newEvents = newEvents;
     }
+    
+    public SubscriptionJson() {
+        this.subscriptionId = null;
+        this.bundleId = null;
+        this.productName = null;
+        this.productCategory = null;
+        this.billingPeriod = null;
+        this.priceList = null;
+        this.events = null;
+        this.deletedEvents = null;
+        this.newEvents = null;
+    }
+    
+    public SubscriptionJson(final Subscription data,
+    		List<SubscriptionReadEventJson> events, List<SubscriptionDeletedEventJson> deletedEvents, List<SubscriptionNewEventJson> newEvents) {
+        this.subscriptionId = data.getId().toString();
+        this.bundleId = data.getBundleId().toString();
+        this.productName = data.getCurrentPlan().getProduct().getName();
+        this.productCategory = data.getCurrentPlan().getProduct().getCategory().toString();
+        this.billingPeriod = data.getCurrentPlan().getBillingPeriod().toString();
+        this.priceList = data.getCurrentPriceList();
+        this.events = events;
+        this.deletedEvents = deletedEvents;
+        this.newEvents = newEvents;
+    }
 
     public String getSubscriptionId() {
         return subscriptionId;
@@ -269,4 +296,75 @@ public class SubscriptionJson {
                 + events + ", deletedEvents=" + deletedEvents + ", newEvents="
                 + newEvents + "]";
     }
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((billingPeriod == null) ? 0 : billingPeriod.hashCode());
+		result = prime * result
+				+ ((bundleId == null) ? 0 : bundleId.hashCode());
+		result = prime * result
+				+ ((priceList == null) ? 0 : priceList.hashCode());
+		result = prime * result
+				+ ((productCategory == null) ? 0 : productCategory.hashCode());
+		result = prime * result
+				+ ((productName == null) ? 0 : productName.hashCode());
+		result = prime * result
+				+ ((subscriptionId == null) ? 0 : subscriptionId.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (equalsNoId(obj) == false) {
+			return false;
+		}
+		SubscriptionJson other = (SubscriptionJson) obj;
+		if (subscriptionId == null) {
+			if (other.subscriptionId != null)
+				return false;
+		} else if (!subscriptionId.equals(other.subscriptionId))
+			return false;
+		return true;
+	}
+
+	public boolean equalsNoId(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		SubscriptionJson other = (SubscriptionJson) obj;
+		if (billingPeriod == null) {
+			if (other.billingPeriod != null)
+				return false;
+		} else if (!billingPeriod.equals(other.billingPeriod))
+			return false;
+		if (bundleId == null) {
+			if (other.bundleId != null)
+				return false;
+		} else if (!bundleId.equals(other.bundleId))
+			return false;
+		if (priceList == null) {
+			if (other.priceList != null)
+				return false;
+		} else if (!priceList.equals(other.priceList))
+			return false;
+		if (productCategory == null) {
+			if (other.productCategory != null)
+				return false;
+		} else if (!productCategory.equals(other.productCategory))
+			return false;
+		if (productName == null) {
+			if (other.productName != null)
+				return false;
+		} else if (!productName.equals(other.productName))
+			return false;
+		return true;
+	}
+    
+    
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index 7601229..c3d0ffa 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -19,6 +19,7 @@ package com.ning.billing.jaxrs.resources;
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
 import java.net.URI;
+import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
@@ -35,11 +36,11 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.Response.Status;
 
-import org.codehaus.jackson.annotate.JsonCreator;
-import org.codehaus.jackson.annotate.JsonProperty;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.ning.billing.ErrorCode;
@@ -50,7 +51,9 @@ import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJson;
 import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
 
 
 @Singleton
@@ -60,12 +63,14 @@ public class AccountResource implements BaseJaxrsResource {
     private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
 
     private final AccountUserApi accountApi;
-    final EntitlementUserApi entitlementApi;
+    private final EntitlementUserApi entitlementApi;
     private final Context context;
+    private final JaxrsUriBuilder uriBuilder;
 
     @Inject
-    public AccountResource(final AccountUserApi accountApi, final EntitlementUserApi entitlementApi, final Context context) {
-        this.accountApi = accountApi;
+    public AccountResource(final JaxrsUriBuilder uriBuilder, final AccountUserApi accountApi, final EntitlementUserApi entitlementApi, final Context context) {
+        this.uriBuilder = uriBuilder;
+    	this.accountApi = accountApi;
         this.entitlementApi = entitlementApi;
         this.context = context;
     }
@@ -76,7 +81,7 @@ public class AccountResource implements BaseJaxrsResource {
     public Response getAccount(@PathParam("accountId") String accountId) {
         Account account = accountApi.getAccountById(UUID.fromString(accountId));
         if (account == null) {
-            return Response.status(Status.NO_CONTENT).build();
+        	return Response.status(Status.NO_CONTENT).build();
         }
         AccountJson json = new AccountJson(account);
         return Response.status(Status.OK).entity(json).build();
@@ -86,12 +91,20 @@ public class AccountResource implements BaseJaxrsResource {
     @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
     @Produces(APPLICATION_JSON)
     public Response getAccountBundles(@PathParam("accountId") String accountId) {
+
     	UUID uuid = UUID.fromString(accountId);
+    	Account account = accountApi.getAccountById(uuid);
+    	if (account == null) {
+    		return Response.status(Status.NO_CONTENT).build();    		
+    	}
     	List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(uuid);
-    	// STEPH should we fetch account first to make sure id exists or what's the deal here
-    	// 
-    	String json = null;
-        return Response.status(Status.OK).entity(json).build();
+    	Collection<BundleJson> result = Collections2.transform(bundles, new Function<SubscriptionBundle, BundleJson>() {
+			@Override
+			public BundleJson apply(SubscriptionBundle input) {
+				return new BundleJson(input);
+			}
+		});
+        return Response.status(Status.OK).entity(result).build();
     }
 
     
@@ -120,12 +133,7 @@ public class AccountResource implements BaseJaxrsResource {
             AccountData data = json.toAccountData();
             final Account account = accountApi.createAccount(data, null, null, context.getContext());
             URI uri = UriBuilder.fromPath(account.getId().toString()).build();
-            Response.ResponseBuilder ri = Response.created(uri);
-            return ri.entity(new Object() {
-                public URI getUri() {
-                    return UriBuilder.fromResource(AccountResource.class).path(AccountResource.class, "getAccount").build(account.getId());
-                }
-            }).build();
+            return uriBuilder.buildResponse(AccountResource.class, "getAccount", account.getId());
         } catch (AccountApiException e) {
             log.info(String.format("Failed to create account %s", json), e);
             return Response.status(Status.BAD_REQUEST).build();
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
index 9eb01eb..02d9f86 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
@@ -29,11 +29,16 @@ public interface BaseJaxrsResource {
 	 * Query parameters
 	 */
 	public static final String QUERY_EXTERNAL_KEY = "external_key";
+	public static final String QUERY_REQUESTED_DT = "requested_date";	
 	
 	
 	public static final String ACCOUNTS = "accounts";	
 	public static final String ACCOUNTS_PATH = API_PREFIX + API_VERSION + "/" + ACCOUNTS;
 	
 	public static final String BUNDLES = "bundles";		
+	public static final String BUNDLES_PATH = API_PREFIX + API_VERSION + "/" + BUNDLES;
+
+	public static final String SUBSCRIPTIONS = "subscriptions";		
+	public static final String SUBSCRIPTIONS_PATH = API_PREFIX + API_VERSION + "/" + SUBSCRIPTIONS;
 
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
index ba9405e..90c9e81 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -18,6 +18,10 @@ package com.ning.billing.jaxrs.resources;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -28,29 +32,91 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.inject.Inject;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+
+@Path(BaseJaxrsResource.BUNDLES_PATH)
+public class BundleResource implements BaseJaxrsResource {
+
+	private static final Logger log = LoggerFactory.getLogger(BundleResource.class);
+
+	private final EntitlementUserApi entitlementApi;
+	private final Context context;
+    private final JaxrsUriBuilder uriBuilder;	
+
+    @Inject
+	public BundleResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi, final Context context) {
+	    this.uriBuilder = uriBuilder;
+		this.entitlementApi = entitlementApi;
+		this.context = context;
+	}
+
+	@GET
+	@Path("/{bundleId:"  + UUID_PATTERN + "}")
+	@Produces(APPLICATION_JSON)
+	public Response getBundle(@PathParam("bundleId") final String bundleId) {
+		SubscriptionBundle bundle = entitlementApi.getBundleFromId(UUID.fromString(bundleId));
+		if (bundle == null) {
+			return Response.status(Status.NO_CONTENT).build();
+		}
+		BundleJson json = new BundleJson(bundle);
+		return Response.status(Status.OK).entity(json).build();
+	}
 
-@Path("/1.0/bundle")
-public class BundleResource {
-
-    @GET
-    @Path("/{bundleId:\\\\w+-\\\\w+-\\\\w+-\\\\w+-\\\\w+}")
-    @Produces(APPLICATION_JSON)
-    public Response getBundle(@PathParam("bundleId") String bundleId) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @GET
-    @Produces(APPLICATION_JSON)
-    public Response getBundleByKey(@QueryParam("externalKey") String externalKey) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @POST
-    @Consumes(APPLICATION_JSON)
-    @Produces(APPLICATION_JSON)
-    public Response createBundle(BundleJson bundle) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
+	@GET
+	@Produces(APPLICATION_JSON)
+	public Response getBundleByKey(@QueryParam(QUERY_EXTERNAL_KEY) final String externalKey) {
+		SubscriptionBundle bundle = entitlementApi.getBundleForKey(externalKey);
+		if (bundle == null) {
+			return Response.status(Status.NO_CONTENT).build();
+		}
+		BundleJson json = new BundleJson(bundle);
+		return Response.status(Status.OK).entity(json).build();
+	}
 
+	@POST
+	@Consumes(APPLICATION_JSON)
+	@Produces(APPLICATION_JSON)
+	public Response createBundle(final BundleJson json) {
+		try {
+			UUID accountId = UUID.fromString(json.getAccountId());
+			final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(), context.getContext());
+            return uriBuilder.buildResponse(BundleResource.class, "getBundle", bundle.getId());
+		} catch (EntitlementUserApiException e) {
+			log.info(String.format("Failed to create bundle %s", json), e);
+			return Response.status(Status.BAD_REQUEST).build();
+		}
+	}
+	
+	@GET
+	@Path("/{bundleId:" + UUID_PATTERN + "}/" + SUBSCRIPTIONS)
+	@Produces(APPLICATION_JSON)
+	public Response getBundleSubscriptions(@PathParam("bundleId") final String bundleId) {
+		
+		UUID uuid = UUID.fromString(bundleId);
+		SubscriptionBundle bundle = entitlementApi.getBundleFromId(uuid);
+		if (bundle == null) {
+			return Response.status(Status.NO_CONTENT).build();
+		}
+		List<Subscription> bundles = entitlementApi.getSubscriptionsForBundle(uuid);
+		Collection<SubscriptionJson> result =  Collections2.transform(bundles, new Function<Subscription, SubscriptionJson>() {
+			@Override
+			public SubscriptionJson apply(Subscription input) {
+				return new SubscriptionJson(input, null, null, null);
+			}
+		});
+		return Response.status(Status.OK).entity(result).build();
+	}
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
index 943ef50..e4b5893 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -18,6 +18,8 @@ package com.ning.billing.jaxrs.resources;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
+import java.util.UUID;
+
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
@@ -31,50 +33,141 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
 import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import com.google.inject.Inject;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
+import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.clock.Clock;
+
+@Path(BaseJaxrsResource.SUBSCRIPTIONS_PATH)
+public class SubscriptionResource implements BaseJaxrsResource{
+
+	private static final Logger log = LoggerFactory.getLogger(SubscriptionResource.class);
+
+	private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+
+	private final EntitlementUserApi entitlementApi;
+	private final Context context;
+	private final JaxrsUriBuilder uriBuilder;	
+	private final Clock clock;
+
+	@Inject
+	public SubscriptionResource(final JaxrsUriBuilder uriBuilder, final EntitlementUserApi entitlementApi, final Clock clock, final Context context) {
+		this.uriBuilder = uriBuilder;
+		this.entitlementApi = entitlementApi;
+		this.context = context;
+		this.clock = clock;
+	}
+
+	@GET
+	@Path("/{subscriptionId:" + UUID_PATTERN + "}")
+	@Produces(APPLICATION_JSON)
+	public Response getSubscription(@PathParam("subscriptionId") final String subscriptionId) {
+
+		UUID uuid = UUID.fromString(subscriptionId);
+		Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
+		if (subscription == null) {
+			return Response.status(Status.NO_CONTENT).build();
+		}
+		SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
+		return Response.status(Status.OK).entity(json).build();
+	}
+
+	@POST
+	@Consumes(APPLICATION_JSON)
+	@Produces(APPLICATION_JSON)
+	public Response createSubscription(SubscriptionJson subscription,
+			@QueryParam(QUERY_REQUESTED_DT) String requestedDate) {
+
+		try {
+			DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;		
+			UUID uuid = UUID.fromString(subscription.getBundleId());
+
+			PlanPhaseSpecifier spec =  new PlanPhaseSpecifier(subscription.getProductName(),
+					ProductCategory.valueOf(subscription.getProductCategory()),
+					BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), null);
+			Subscription created = entitlementApi.createSubscription(uuid, spec, inputDate, context.getContext());
+			return uriBuilder.buildResponse(SubscriptionResource.class, "getSubscription", created.getId());
+
+		} catch (EntitlementUserApiException e) {
+			log.info(String.format("Failed to create subscription %s", subscription), e);
+			return Response.status(Status.BAD_REQUEST).build();
+		}
+	}
+
+	@PUT
+	@Produces(APPLICATION_JSON)
+	@Consumes(APPLICATION_JSON)
+	@Path("/{subscriptionId:" + UUID_PATTERN + "}")
+	public Response changeSubscriptionPlan(SubscriptionJson subscription,
+			@PathParam("subscriptionId") String subscriptionId,
+			@QueryParam(QUERY_REQUESTED_DT) String requestedDate) {
+
+		try {
+			UUID uuid = UUID.fromString(subscriptionId);
+			Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+			if (current == null) {
+				return Response.status(Status.NO_CONTENT).build();
+			}
+			DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+			current.changePlan(subscription.getProductName(),  BillingPeriod.valueOf(subscription.getBillingPeriod()), subscription.getPriceList(), inputDate, context.getContext());
+
+			return getSubscription(subscriptionId);
+		} catch (EntitlementUserApiException e) {
+			log.info(String.format("Failed to change plan for subscription %s", subscription), e);
+			return Response.status(Status.BAD_REQUEST).build();
+		}
+	}
+
+	@PUT
+	@Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
+	@Produces(APPLICATION_JSON)
+	public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") String subscriptionId) {
+		try {
+			UUID uuid = UUID.fromString(subscriptionId);
+			Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+			if (current == null) {
+				return Response.status(Status.NO_CONTENT).build();
+			}
+			current.uncancel(context.getContext());
+			return Response.status(Status.OK).build();
+		} catch (EntitlementUserApiException e) {
+			log.info(String.format("Failed to uncancel plan for subscription %s", subscriptionId), e);
+			return Response.status(Status.BAD_REQUEST).build();
+		}
+	}
+
+	@DELETE
+	@Path("/{subscriptionId:" + UUID_PATTERN + "}")
+	@Produces(APPLICATION_JSON)
+	public Response cancelSubscriptionPlan(@PathParam("subscriptionId") String subscriptionId,
+			@QueryParam(QUERY_REQUESTED_DT) String requestedDate) {
 
-@Path("/1.0/subscription")
-public class SubscriptionResource {
-
-    @GET
-    @Path("/{subscriptionId:\\w+-\\w+-\\w+-\\w+-\\w+}")
-    @Produces(APPLICATION_JSON)
-    public Response getSubscription(@PathParam("subscriptionId") String subscriptionId) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @POST
-    @Consumes(APPLICATION_JSON)
-    @Produces(APPLICATION_JSON)
-    public Response createSubscription(SubscriptionJson subscription,
-            @QueryParam("requestedDate") String requestedDate) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @PUT
-    @Produces(APPLICATION_JSON)
-    @Consumes(APPLICATION_JSON)
-    @Path("/{subscriptionId:\\w+-\\w+-\\w+-\\w+-\\w+}")
-    public Response changeSubscriptionPlan(SubscriptionJson subscription,
-            @PathParam("subscriptionId") String subscriptionId,
-            @QueryParam("requestedDate") String requestedDate) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @PUT
-    @Path("/{subscriptionId:\\w+-\\w+-\\w+-\\w+-\\w+}/uncancel")
-    @Produces(APPLICATION_JSON)
-    public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") String subscriptionId) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
-
-    @DELETE
-    @Path("/{subscriptionId:\\w+-\\w+-\\w+-\\w+-\\w+}")
-    @Produces(APPLICATION_JSON)
-    public Response cancelSubscriptionPlan(@PathParam("subscriptionId") String subscriptionId,
-            @QueryParam("requestedDate") String requestedDate) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
-    }
+		try {
+			UUID uuid = UUID.fromString(subscriptionId);
+			Subscription current = entitlementApi.getSubscriptionFromId(uuid);
+			if (current == null) {
+				return Response.status(Status.NO_CONTENT).build();
+			}
+			DateTime inputDate = (requestedDate != null) ? DATE_TIME_FORMATTER.parseDateTime(requestedDate) : null;
+			current.cancel(inputDate, false, context.getContext());
+			return Response.status(Status.OK).build();
+			
+		} catch (EntitlementUserApiException e) {
+			log.info(String.format("Failed to cancel plan for subscription %s", subscriptionId), e);
+			return Response.status(Status.BAD_REQUEST).build();
+		}
+	}
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
new file mode 100644
index 0000000..a38a9fe
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -0,0 +1,39 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs.util;
+
+import java.net.URI;
+import java.util.UUID;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+
+public class JaxrsUriBuilder {
+
+	
+	public Response buildResponse(final Class<? extends BaseJaxrsResource> theClass, final String getMethodName, final UUID objectId) {
+		URI uri = UriBuilder.fromPath(objectId.toString()).build();
+		Response.ResponseBuilder ri = Response.created(uri);
+		return ri.entity(new Object() {
+			@SuppressWarnings(value = "all")
+			public URI getUri() {
+				return UriBuilder.fromResource(theClass).path(theClass, getMethodName).build(objectId);
+			}
+		}).build();
+	}
+}
diff --git a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
index e7e8b1e..153c63b 100644
--- a/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
+++ b/payment/src/main/java/com/ning/billing/payment/api/DefaultPaymentApi.java
@@ -214,7 +214,7 @@ public class DefaultPaymentApi implements PaymentApi {
         PaymentInfo paymentInfo = null;
 
         if (paymentOrError.isLeft()) {
-            String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getType(), 0, 100);
+            String error = StringUtils.substring(paymentOrError.getLeft().getMessage() + paymentOrError.getLeft().getBusEventType(), 0, 100);
             log.info("Could not process a payment for " + paymentAttempt + " error was " + error);
 
             scheduleRetry(paymentAttempt, error);
diff --git a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
index 0e76771..4fa635e 100644
--- a/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
+++ b/payment/src/test/java/com/ning/billing/payment/util/TestSyncWaitOnEventBus.java
@@ -49,6 +49,11 @@ public class TestSyncWaitOnEventBus {
         public String getMsg() {
             return msg;
         }
+
+		@Override
+		public BusEventType getBusEventType() {
+			return null;
+		}
     }
 
     private static final class TestResponse implements EventBusResponse<UUID> {
@@ -68,6 +73,11 @@ public class TestSyncWaitOnEventBus {
         public String getMsg() {
             return msg;
         }
+
+		@Override
+		public BusEventType getBusEventType() {
+			return null;
+		}
     }
 
     private Bus eventBus;

pom.xml 7(+7 -0)

diff --git a/pom.xml b/pom.xml
index 8844cbc..b50bc5f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,13 @@
             </dependency>
             <dependency>
                 <groupId>com.ning.billing</groupId>
+                <artifactId>killbill-beatrix</artifactId>
+                <version>${project.version}</version>
+                <type>test-jar</type>
+                <scope>test</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.ning.billing</groupId>
                 <artifactId>killbill-analytics</artifactId>
                 <version>${project.version}</version>
             </dependency>

server/pom.xml 6(+6 -0)

diff --git a/server/pom.xml b/server/pom.xml
index a624843..6280399 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -250,6 +250,12 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-beatrix</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-mxj</artifactId>
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
index 51091e5..424c10f 100644
--- a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -22,6 +22,7 @@ import com.ning.billing.server.config.KillbillServerConfig;
 import com.ning.billing.server.healthchecks.KillbillHealthcheck;
 import com.ning.billing.server.modules.KillbillServerModule;
 import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.BusEvent;
 import com.ning.billing.util.bus.BusService;
 import com.ning.jetty.base.modules.ServerModuleBuilder;
 import com.ning.jetty.core.listeners.SetupServer;
@@ -127,7 +128,7 @@ public class KillbillGuiceListener extends SetupServer
          * IRS event handler for killbill entitlement events
          */
         @Subscribe
-        public void handleEntitlementevents(SubscriptionTransition event)
+        public void handleEntitlementevents(BusEvent event)
         {
             logger.info("Killbill entitlement event {}", event.toString());
         }
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index 902f69e..f1fd9a8 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -64,6 +64,10 @@ public class KillbillServerModule extends AbstractModule
         bind(PaymentResource.class).asEagerSingleton();
     }
 
+    protected void installClock() {
+        install(new ClockModule());    	
+    }
+    
     protected void installKillbillModules() {
         install(new FieldStoreModule());
         install(new TagStoreModule());
@@ -77,6 +81,6 @@ public class KillbillServerModule extends AbstractModule
         install(new AnalyticsModule());
         install(new PaymentModule());
         install(new BeatrixModule());
-        install(new ClockModule());
+        installClock();
     }
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
index 2b597df..4f0a4b2 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -42,30 +42,16 @@ public class TestAccount extends TestJaxrsBase {
 	@Test(groups="slow", enabled=true)
 	public void testAccountOk() throws Exception {
 		
-		AccountJson input = getAccountJson("xoxo", "shdgfhwe", "xoxo@yahoo.com");
-		String baseJson = mapper.writeValueAsString(input);
-		Response response = doPost(BaseJaxrsResource.ACCOUNTS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-		Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
-
-		String location = response.getHeader("Location");
-		Assert.assertNotNull(location);
-
-		// Retrieves by Id based on Location returned
-		response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
-		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-
-		baseJson = response.getResponseBody();
-		AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
-		Assert.assertTrue(objFromJson.equalsNoId(input));
-
+		AccountJson input = createAccount("xoxo", "shdgfhwe", "xoxo@yahoo.com");
+		
 		// Retrieves by external key
 		Map<String, String> queryParams = new HashMap<String, String>();
 		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "shdgfhwe");
-		response = doGet(BaseJaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Response response = doGet(BaseJaxrsResource.ACCOUNTS_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
 		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
-		baseJson = response.getResponseBody();
-		objFromJson = mapper.readValue(baseJson, AccountJson.class);
-		Assert.assertTrue(objFromJson.equalsNoId(input));
+		String baseJson = response.getResponseBody();
+		AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertTrue(objFromJson.equals(input));
 		
 		// Update Account
 		AccountJson newInput = new AccountJson(objFromJson.getAcountId(),
@@ -82,7 +68,7 @@ public class TestAccount extends TestJaxrsBase {
 
 	@Test(groups="slow", enabled=true)
 	public void testUpdateNonExistentAccount() throws Exception {
-		AccountJson input = getAccountJson("xoxo", "shdgfhwe", "xoxo@yahoo.com");
+		AccountJson input = getAccountJson("xoxo", "shghaahwe", "xoxo@yahoo.com");
 		String baseJson = mapper.writeValueAsString(input);
 		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + input.getAcountId();
 		Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
new file mode 100644
index 0000000..d340ab8
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -0,0 +1,116 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.codehaus.jackson.type.TypeReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestBundle extends TestJaxrsBase {
+
+	private static final Logger log = LoggerFactory.getLogger(TestBundle.class);
+
+
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleOk() throws Exception {
+
+		AccountJson accountJson = createAccount("xlxl", "shdgfhkkl", "xlxl@yahoo.com");
+		BundleJson bundleJson = createBundle(accountJson.getAcountId(), "12345");
+		
+		// Retrieves by external key
+		Map<String, String> queryParams = new HashMap<String, String>();
+		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "12345");
+		Response response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		BundleJson objFromJson = mapper.readValue(baseJson, BundleJson.class);
+		Assert.assertTrue(objFromJson.equals(bundleJson));
+	}
+	
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleFromAccount() throws Exception {
+
+		AccountJson accountJson = createAccount("xaxa", "saagfhkkl", "xaxa@yahoo.com");
+		BundleJson bundleJson1 = createBundle(accountJson.getAcountId(), "156567");
+		BundleJson bundleJson2 = createBundle(accountJson.getAcountId(), "265658");
+
+		String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAcountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		List<BundleJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJson>>() {});
+		
+		Collections.sort(objFromJson, new Comparator<BundleJson>() {
+			@Override
+			public int compare(BundleJson o1, BundleJson o2) {
+				return o1.getExternalKey().compareTo(o2.getExternalKey());
+			}
+		});
+		Assert.assertEquals(objFromJson.get(0), bundleJson1);
+		Assert.assertEquals(objFromJson.get(1), bundleJson2);		
+	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testBundleNonExistent() throws Exception {
+		AccountJson accountJson = createAccount("dfdf", "dfdfgfhkkl", "dfdf@yahoo.com");	
+		
+		String uri = BaseJaxrsResource.BUNDLES_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747";
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		
+		
+		// Retrieves by external key
+		Map<String, String> queryParams = new HashMap<String, String>();
+		queryParams.put(BaseJaxrsResource.QUERY_EXTERNAL_KEY, "56566");
+		response = doGet(BaseJaxrsResource.BUNDLES_PATH, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		
+		
+		uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + accountJson.getAcountId().toString() + "/" + BaseJaxrsResource.BUNDLES;
+		response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		String baseJson = response.getResponseBody();
+		List<BundleJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<BundleJson>>() {});
+		Assert.assertNotNull(objFromJson);
+		Assert.assertEquals(objFromJson.size(), 0);
+	}
+
+	@Test(groups="slow", enabled=true)
+	public void testAppNonExistent() throws Exception {
+		String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/99999999-b103-42f3-8b6e-dd244f1d0747/" + BaseJaxrsResource.BUNDLES;
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());	
+	}
+	
+	
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 4db4a3b..ec2b3ea 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -28,6 +28,8 @@ import java.util.Map.Entry;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
+import javax.ws.rs.core.Response.Status;
+
 import org.codehaus.jackson.map.ObjectMapper;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.skife.config.ConfigurationObjectFactory;
@@ -36,16 +38,24 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeSuite;
 
 import com.google.inject.Injector;
 import com.google.inject.Module;
+import com.ning.billing.beatrix.integration.TestBusHandler;
+import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.dbi.DBIProvider;
 import com.ning.billing.dbi.DbiConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.billing.server.listeners.KillbillGuiceListener;
 import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.clock.ClockMock;
 import com.ning.http.client.AsyncCompletionHandler;
 import com.ning.http.client.AsyncHttpClient;
 import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
@@ -71,7 +81,8 @@ public class TestJaxrsBase {
 	protected CoreConfig config;
 	protected AsyncHttpClient httpClient;	
 	protected ObjectMapper mapper;
-
+	protected ClockMock clock;
+	protected TestBusHandler busHandler;
 
 	public static void loadSystemPropertiesFromClasspath(final String resource) {
 		final URL url = TestJaxrsBase.class.getResource(resource);
@@ -99,6 +110,11 @@ public class TestJaxrsBase {
 	public static class TestKillbillServerModule extends KillbillServerModule {
 		
 		@Override
+	    protected void installClock() {
+			bind(Clock.class).to(ClockMock.class).asEagerSingleton();
+	    }
+		
+		@Override
 		protected void configureDao() {
 			final MysqlTestingHelper helper = new MysqlTestingHelper();
 			bind(MysqlTestingHelper.class).toInstance(helper);
@@ -113,9 +129,13 @@ public class TestJaxrsBase {
 	    }
 	}
 
-
+	@BeforeMethod(groups="slow")
+	public void cleanupTables() {
+		helper.cleanupAllTables();
+		busHandler.reset();
+	}
 	
-	@BeforeClass(groups="slow")
+	@BeforeSuite(groups="slow")
 	public void setup() throws Exception {
 
 		loadSystemPropertiesFromClasspath("/killbill.properties");
@@ -137,9 +157,10 @@ public class TestJaxrsBase {
 		server.start();
 		
 		Injector injector = ((TestKillbillGuiceListener) eventListener).getTheInjector();
-		
 		helper = injector.getInstance(MysqlTestingHelper.class);
-		helper.cleanupAllTables();
+		clock = (ClockMock) injector.getInstance(Clock.class);
+		
+		busHandler = new TestBusHandler();
 	}
 	
 	@AfterClass(groups="slow")
@@ -152,6 +173,72 @@ public class TestJaxrsBase {
 		}
 	}
 
+	
+	protected AccountJson createAccount(String name, String key, String email) throws Exception {
+		AccountJson input = getAccountJson(name, key, email);
+		String baseJson = mapper.writeValueAsString(input);
+		Response response = doPost(BaseJaxrsResource.ACCOUNTS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+		String location = response.getHeader("Location");
+		Assert.assertNotNull(location);
+
+		// Retrieves by Id based on Location returned
+		response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		
+		baseJson = response.getResponseBody();
+		AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertNotNull(objFromJson);
+		return objFromJson;
+	}
+	
+	
+	
+	protected BundleJson createBundle(String accountId, String key) throws Exception {
+		BundleJson input = new BundleJson(null, accountId, key, null);
+		String baseJson = mapper.writeValueAsString(input);
+		Response response = doPost(BaseJaxrsResource.BUNDLES_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+		String location = response.getHeader("Location");
+		Assert.assertNotNull(location);
+
+		// Retrieves by Id based on Location returned
+		response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+		baseJson = response.getResponseBody();
+		BundleJson objFromJson = mapper.readValue(baseJson, BundleJson.class);
+		Assert.assertTrue(objFromJson.equalsNoId(input));
+		return objFromJson;
+	}
+	
+	protected SubscriptionJson createSubscription(final String bundleId, final String productName, final String productCategory, final String billingPeriod) throws Exception {
+		
+		SubscriptionJson input = new SubscriptionJson(null, bundleId, productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null, null, null);
+		String baseJson = mapper.writeValueAsString(input);
+		Response response = doPost(BaseJaxrsResource.SUBSCRIPTIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+		String location = response.getHeader("Location");
+		Assert.assertNotNull(location);
+
+		// Retrieves by Id based on Location returned
+		response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+		baseJson = response.getResponseBody();
+		SubscriptionJson objFromJson = mapper.readValue(baseJson, SubscriptionJson.class);
+		Assert.assertTrue(objFromJson.equalsNoId(input));
+		return objFromJson;
+	}
+
+
+	
+	//
+	// HTTP CLIENT HELPERS
+	//
 	protected Response doPost(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
 		BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("POST", getUrlFromUri(uri), queryParams);
 		if (body != null) {
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
new file mode 100644
index 0000000..8b2a055
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestSubscription.java
@@ -0,0 +1,112 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.jaxrs;
+
+import static org.testng.Assert.assertTrue;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.Duration;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.catalog.api.TimeUnit;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJson;
+import com.ning.billing.jaxrs.json.SubscriptionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestSubscription extends TestJaxrsBase {
+	
+	private static final Logger log = LoggerFactory.getLogger(TestSubscription.class);
+
+	private static final long DELAY = 5000;
+
+	@Test(groups="slow", enabled=true)
+	public void testSubscriptionOk() throws Exception {
+
+		AccountJson accountJson = createAccount("xil", "shdxilhkkl", "xil@yahoo.com");
+		BundleJson bundleJson = createBundle(accountJson.getAcountId(), "99999");
+		
+        String productName = "Shotgun";
+        BillingPeriod term = BillingPeriod.MONTHLY;
+
+        //busHandler.pushExpectedEvent(NextEvent.CREATE);
+        //busHandler.pushExpectedEvent(NextEvent.INVOICE);
+		SubscriptionJson subscriptionJson = createSubscription(bundleJson.getBundleId(), productName, ProductCategory.BASE.toString(), term.toString());
+		//assertTrue(busHandler.isCompleted(DELAY));
+		
+		// Change plan IMM
+		String newProductName = "Assault-Rifle";
+	
+		SubscriptionJson newInput = new SubscriptionJson(subscriptionJson.getSubscriptionId(),
+				subscriptionJson.getBundleId(),
+				newProductName,
+				subscriptionJson.getProductCategory(), 
+				subscriptionJson.getBillingPeriod(), 
+				subscriptionJson.getPriceList(), null, null, null);
+		String baseJson = mapper.writeValueAsString(newInput);
+		
+		String uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+		Response response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		baseJson = response.getResponseBody();
+		SubscriptionJson objFromJson = mapper.readValue(baseJson, SubscriptionJson.class);
+		Assert.assertTrue(objFromJson.equals(newInput));
+		
+		// MOVE after TRIAL
+	    //busHandler.pushExpectedEvent(NextEvent.PHASE);
+	    //busHandler.pushExpectedEvent(NextEvent.INVOICE);
+	    //busHandler.pushExpectedEvent(NextEvent.PAYMENT);
+		clock.setDeltaFromReality(new Duration() {
+			@Override
+			public TimeUnit getUnit() {
+				return TimeUnit.MONTHS;
+			}
+			@Override
+			public int getNumber() {
+				return 1;
+			}
+			@Override
+			public DateTime addToDateTime(DateTime dateTime) {
+				return null;
+			}
+		}, 1000);
+		//assertTrue(busHandler.isCompleted(DELAY));		
+		
+		
+		Thread.sleep(5000);
+		
+		// Cancel EOT
+		uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString();
+		response = doDelete(uri, DEFAULT_EMPTY_QUERY, 10000 /* DEFAULT_HTTP_TIMEOUT_SEC */);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		
+		
+		// Uncancel
+		uri = BaseJaxrsResource.SUBSCRIPTIONS_PATH + "/" + subscriptionJson.getSubscriptionId().toString() + "/uncancel";
+		response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, 10000 /* DEFAULT_HTTP_TIMEOUT_SEC */);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+	}
+
+}
diff --git a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
index 2310f1c..2078d0e 100644
--- a/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
+++ b/util/src/test/java/com/ning/billing/util/bus/TestEventBus.java
@@ -50,6 +50,11 @@ public class TestEventBus {
             this.name = name;
             this.value = value;
         }
+
+		@Override
+		public BusEventType getBusEventType() {
+			return null;
+		}
     }
 
     public static final class MyOtherEvent implements BusEvent {
@@ -60,6 +65,11 @@ public class TestEventBus {
             this.name = name;
             this.value = value;
         }
+
+		@Override
+		public BusEventType getBusEventType() {
+			return null;
+		}
     }
 
     public static class MyEventHandler {