killbill-aplcache

. Fix server issues . Implement Account jaxrs resource (except

4/6/2012 11:06:58 PM

Details

diff --git a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
index 7717aae..3daf37d 100644
--- a/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
+++ b/account/src/main/java/com/ning/billing/account/api/user/DefaultAccountUserApi.java
@@ -98,7 +98,7 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
         try {
             dao.update(account, context);
         } catch (EntityPersistenceException e) {
-            throw new AccountApiException(e, ErrorCode.ACCOUNT_UPDATE_FAILED);
+            throw new AccountApiException(e, e.getCode(), e.getMessage());
         }
   
     }
@@ -106,10 +106,9 @@ public class DefaultAccountUserApi implements com.ning.billing.account.api.Accou
     @Override
     public void updateAccount(final String externalKey, final AccountData accountData, final CallContext context) throws AccountApiException {
     	UUID accountId = getIdFromKey(externalKey);
-    	if(accountId == null) {
+    	if (accountId == null) {
     		throw new AccountApiException(ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_KEY, externalKey);
     	}
-
     	updateAccount(accountId, accountData, context);
      }
 
diff --git a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
index bfc97e8..2f6ea8b 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/notification/DefaultNextBillingDateNotifier.java
@@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.ning.billing.config.InvoiceConfig;
+import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.invoice.InvoiceListener;
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
index 8af2657..13ad140 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountJson.java
@@ -44,7 +44,7 @@ public class AccountJson {
     private final String country;
     private final String phone;
 
-/*
+
     public AccountJson(Account account) {
         this.acountId = account.getId().toString();
         this.name = account.getName();
@@ -62,8 +62,6 @@ public class AccountJson {
         this.country = account.getCountry();
         this.phone = account.getPhone();
     }
-
-*/
     
     public AccountData toAccountData() {
         return new AccountData() {
@@ -139,6 +137,25 @@ public class AccountJson {
         };
     }
 
+    // Seems like Jackson (JacksonJsonProvider.readFrom(Class<Object>, Type, Annotation[], MediaType, MultivaluedMap<String,String>, InputStream) line: 443)
+    // needs us to define a default CTOR to instanciate the class first.
+    public AccountJson() {
+        this.acountId = null;
+        this.name = null;
+        this.length = null;
+        this.externalKey = null;
+        this.email = null;
+        this.billCycleDay = null;
+        this.currency = null;
+        this.paymentProvider = null;
+        this.timeZone = null;
+        this.address1 = null;
+        this.address2 = null;
+        this.company = null;
+        this.state = null;
+        this.country = null;
+        this.phone = null;
+    }
 
     @JsonCreator
     public AccountJson(@JsonProperty("account_id") String acountId,
@@ -233,4 +250,131 @@ public class AccountJson {
     public String getPhone() {
         return phone;
     }
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((acountId == null) ? 0 : acountId.hashCode());
+		result = prime * result
+				+ ((address1 == null) ? 0 : address1.hashCode());
+		result = prime * result
+				+ ((address2 == null) ? 0 : address2.hashCode());
+		result = prime * result
+				+ ((billCycleDay == null) ? 0 : billCycleDay.hashCode());
+		result = prime * result + ((company == null) ? 0 : company.hashCode());
+		result = prime * result + ((country == null) ? 0 : country.hashCode());
+		result = prime * result
+				+ ((currency == null) ? 0 : currency.hashCode());
+		result = prime * result + ((email == null) ? 0 : email.hashCode());
+		result = prime * result
+				+ ((externalKey == null) ? 0 : externalKey.hashCode());
+		result = prime * result + ((length == null) ? 0 : length.hashCode());
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result
+				+ ((paymentProvider == null) ? 0 : paymentProvider.hashCode());
+		result = prime * result + ((phone == null) ? 0 : phone.hashCode());
+		result = prime * result + ((state == null) ? 0 : state.hashCode());
+		result = prime * result
+				+ ((timeZone == null) ? 0 : timeZone.hashCode());
+		return result;
+	}
+
+	// Used to check POST versus GET
+	public boolean equalsNoId(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		AccountJson other = (AccountJson) obj;
+		if (address1 == null) {
+			if (other.address1 != null)
+				return false;
+		} else if (!address1.equals(other.address1))
+			return false;
+		if (address2 == null) {
+			if (other.address2 != null)
+				return false;
+		} else if (!address2.equals(other.address2))
+			return false;
+		if (billCycleDay == null) {
+			if (other.billCycleDay != null)
+				return false;
+		} else if (!billCycleDay.equals(other.billCycleDay))
+			return false;
+		if (company == null) {
+			if (other.company != null)
+				return false;
+		} else if (!company.equals(other.company))
+			return false;
+		if (country == null) {
+			if (other.country != null)
+				return false;
+		} else if (!country.equals(other.country))
+			return false;
+		if (currency == null) {
+			if (other.currency != null)
+				return false;
+		} else if (!currency.equals(other.currency))
+			return false;
+		if (email == null) {
+			if (other.email != null)
+				return false;
+		} else if (!email.equals(other.email))
+			return false;
+		if (externalKey == null) {
+			if (other.externalKey != null)
+				return false;
+		} else if (!externalKey.equals(other.externalKey))
+			return false;
+		if (length == null) {
+			if (other.length != null)
+				return false;
+		} else if (!length.equals(other.length))
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		if (paymentProvider == null) {
+			if (other.paymentProvider != null)
+				return false;
+		} else if (!paymentProvider.equals(other.paymentProvider))
+			return false;
+		if (phone == null) {
+			if (other.phone != null)
+				return false;
+		} else if (!phone.equals(other.phone))
+			return false;
+		if (state == null) {
+			if (other.state != null)
+				return false;
+		} else if (!state.equals(other.state))
+			return false;
+		if (timeZone == null) {
+			if (other.timeZone != null)
+				return false;
+		} else if (!timeZone.equals(other.timeZone))
+			return false;
+		return true;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (equalsNoId(obj) == false) {
+			return false;
+		} else {
+			AccountJson other = (AccountJson) obj;
+			if (acountId == null) {
+				if (other.acountId != null)
+					return false;
+			} else if (!acountId.equals(other.acountId))
+				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 0496213..7601229 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.List;
 import java.util.UUID;
 
 import javax.ws.rs.Consumes;
@@ -34,66 +35,88 @@ 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.inject.Inject;
 import com.google.inject.Singleton;
+import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
 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.util.Context;
 
 
 @Singleton
-@Path("/1.0/account")
-public class AccountResource {
+@Path(BaseJaxrsResource.ACCOUNTS_PATH)
+public class AccountResource implements BaseJaxrsResource {
 
     private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
 
     private final AccountUserApi accountApi;
+    final EntitlementUserApi entitlementApi;
     private final Context context;
 
     @Inject
-    public AccountResource(final AccountUserApi accountApi, final Context context) {
+    public AccountResource(final AccountUserApi accountApi, final EntitlementUserApi entitlementApi, final Context context) {
         this.accountApi = accountApi;
+        this.entitlementApi = entitlementApi;
         this.context = context;
     }
 
     @GET
-    @Path("/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
     public Response getAccount(@PathParam("accountId") String accountId) {
         Account account = accountApi.getAccountById(UUID.fromString(accountId));
         if (account == null) {
-            return Response.status(Status.NOT_FOUND).build();
+            return Response.status(Status.NO_CONTENT).build();
         }
-        AccountJson json = null; /* new AccountJson(account); */
+        AccountJson json = new AccountJson(account);
         return Response.status(Status.OK).entity(json).build();
     }
 
     @GET
+    @Path("/{accountId:" + UUID_PATTERN + "}/" + BUNDLES)
     @Produces(APPLICATION_JSON)
-    public Response getAccountByKey(@QueryParam("externalKey") String externalKey) {
+    public Response getAccountBundles(@PathParam("accountId") String accountId) {
+    	UUID uuid = UUID.fromString(accountId);
+    	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();
+    }
+
+    
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getAccountByKey(@QueryParam(QUERY_EXTERNAL_KEY) String externalKey) {
         Account account = null;
         if (externalKey != null) {
             account = accountApi.getAccountByKey(externalKey);
         }
         if (account == null) {
-            return Response.status(Status.NOT_FOUND).build();
+            return Response.status(Status.NO_CONTENT).build();
         }
-        AccountJson json = null; /*  new AccountJson(account); */
+        AccountJson json = new AccountJson(account);
         return Response.status(Status.OK).entity(json).build();
     }
 
+    
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     public Response createAccount(AccountJson json) {
 
         try {
+        	
             AccountData data = json.toAccountData();
             final Account account = accountApi.createAccount(data, null, null, context.getContext());
             URI uri = UriBuilder.fromPath(account.getId().toString()).build();
@@ -112,21 +135,26 @@ public class AccountResource {
     @PUT
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    @Path("/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Path("/{accountId:" + UUID_PATTERN + "}")
     public Response updateAccount(AccountJson json, @PathParam("accountId") String accountId) {
         try {
             AccountData data = json.toAccountData();
-            accountApi.updateAccount(accountId, data, context.getContext());
-            return Response.status(Status.NO_CONTENT).build();
+            UUID uuid = UUID.fromString(accountId);
+            accountApi.updateAccount(uuid, data, context.getContext());
+            return getAccount(accountId);
         } catch (AccountApiException e) {
-            log.info(String.format("Failed to update account %s with %s", accountId, json), e);
-            return Response.status(Status.BAD_REQUEST).build();
+        	if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
+        		return Response.status(Status.NO_CONTENT).build();        		
+        	} else {
+        		log.info(String.format("Failed to update account %s with %s", accountId, json), e);
+        		return Response.status(Status.BAD_REQUEST).build();
+        	}
         }
     }
 
     // Not supported
     @DELETE
-    @Path("/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
+    @Path("/{accountId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
     public Response cancelAccount(@PathParam("accountId") String accountId) {
         /*
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
new file mode 100644
index 0000000..9eb01eb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.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.resources;
+
+public interface BaseJaxrsResource {
+	
+	public static final String API_PREFIX = "";
+	public static final String API_VERSION = "/1.0";
+	
+	/*
+	 * Patterns
+	 */
+	public static String UUID_PATTERN = "\\w+-\\w+-\\w+-\\w+-\\w+";
+	
+	/*
+	 * Query parameters
+	 */
+	public static final String QUERY_EXTERNAL_KEY = "external_key";
+	
+	
+	public static final String ACCOUNTS = "accounts";	
+	public static final String ACCOUNTS_PATH = API_PREFIX + API_VERSION + "/" + ACCOUNTS;
+	
+	public static final String BUNDLES = "bundles";		
+
+}

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

diff --git a/server/pom.xml b/server/pom.xml
index bba0dd1..a624843 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -244,6 +244,22 @@
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
+         <dependency>
+            <groupId>com.ning.billing</groupId>
+            <artifactId>killbill-util</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-mxj-db-files</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
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 df96364..51091e5 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
@@ -15,27 +15,24 @@
  */
 package com.ning.billing.server.listeners;
 
-import java.util.Set;
 
 import com.ning.billing.beatrix.lifecycle.DefaultLifecycle;
 import com.ning.billing.entitlement.api.user.SubscriptionTransition;
-import com.ning.billing.lifecycle.KillbillService;
 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.server.util.ServerUtil;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.bus.BusService;
 import com.ning.jetty.base.modules.ServerModuleBuilder;
 import com.ning.jetty.core.listeners.SetupServer;
-import com.ning.jetty.jdbi.config.DaoConfig;
 
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Injector;
+import com.google.inject.Module;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.management.RuntimeErrorException;
 import javax.servlet.ServletContextEvent;
 
 public class KillbillGuiceListener extends SetupServer
@@ -44,9 +41,14 @@ public class KillbillGuiceListener extends SetupServer
 
     private DefaultLifecycle killbillLifecycle;
     private BusService killbillBusService;
-
     private KillbillEventHandler killbilleventHandler;
 
+    protected Injector theInjector;
+    
+    protected Module getModule() {
+    	return new KillbillServerModule();
+    }
+
     @Override
     public void contextInitialized(ServletContextEvent event)
     {
@@ -56,30 +58,18 @@ public class KillbillGuiceListener extends SetupServer
                 .addConfig(KillbillServerConfig.class)
                 .addHealthCheck(KillbillHealthcheck.class)
                 .addJMXExport(KillbillHealthcheck.class)
-                .addModule(new KillbillServerModule())
+                .addModule(getModule())
                 .addJerseyResource("com.ning.billing.jaxrs.resources");
-/*
-        //
-        // Dynamically add all killbill configs
-        //
-        try {
-        	Set<Class<?>> configs = ServerUtil.getKillbillConfig("com.ning.billing.config", "killbill-api", "com.ning.billing.config.KillbillConfig"); 
-        	for (Class<?> cur : configs) {
-        		builder.addConfig(cur);
-        	}
-        } catch(ClassNotFoundException e) {
-        	throw new RuntimeException(e);
-        }
-        */
+
 
         guiceModule = builder.build();
 
         super.contextInitialized(event);
 
         logger.info("KillbillLifecycleListener : contextInitialized");
-        final Injector injector = injector(event);
-        killbillLifecycle = injector.getInstance(DefaultLifecycle.class);
-        killbillBusService = injector.getInstance(BusService.class);
+        theInjector = injector(event);
+        killbillLifecycle = theInjector.getInstance(DefaultLifecycle.class);
+        killbillBusService = theInjector.getInstance(BusService.class);
 
         killbilleventHandler = new KillbillEventHandler();
 
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 5f03b42..902f69e 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
@@ -44,21 +44,18 @@ import org.skife.jdbi.v2.IDBI;
 public class KillbillServerModule extends AbstractModule
 {
     @Override
-    protected void configure()
-    {
+    protected void configure() {
         configureDao();
         configureResources();
         installKillbillModules();
     }
 
-    protected void configureDao()
-    {
+    protected void configureDao() {
         bind(IDBI.class).to(DBI.class).asEagerSingleton();
         bind(DBI.class).toProvider(DBIProvider.class).asEagerSingleton();
     }
 
-    protected void configureResources()
-    {
+    protected void configureResources() {
         bind(AccountResource.class).asEagerSingleton();
         bind(BundleResource.class).asEagerSingleton();
         bind(SubscriptionResource.class).asEagerSingleton();
@@ -67,8 +64,7 @@ public class KillbillServerModule extends AbstractModule
         bind(PaymentResource.class).asEagerSingleton();
     }
 
-    protected void installKillbillModules()
-    {
+    protected void installKillbillModules() {
         install(new FieldStoreModule());
         install(new TagStoreModule());
         install(new CatalogModule());
diff --git a/server/src/main/resources/killbill-server.properties b/server/src/main/resources/killbill-server.properties
index facf47d..daaa110 100644
--- a/server/src/main/resources/killbill-server.properties
+++ b/server/src/main/resources/killbill-server.properties
@@ -1,6 +1,7 @@
-com.ning.core.dao.url=jdbc:mysql://127.0.0.1:3306/killbill
-com.ning.core.dao.user=root
-com.ning.core.dao.password=root
+# Use skeleton properties for server and configure killbill database
+com.ning.jetty.jdbi.url=jdbc:mysql://127.0.0.1:3306/killbill
+com.ning.jetty.jdbi.user=root
+com.ning.jetty.jdbi.password=root
 
 killbill.catalog.uri=file:src/main/resources/catalog-demo.xml
 
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 6eabeda..2b597df 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -15,205 +15,94 @@
  */
 package com.ning.billing.jaxrs;
 
-import static org.testng.Assert.assertNotNull;
 
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.EventListener;
 import java.util.HashMap;
-import java.util.Iterator;
+import java.util.Map;
+
+
+import javax.ws.rs.core.Response.Status;
 
-import java.util.UUID;
 
-import org.codehaus.jackson.map.ObjectMapper;
-import org.codehaus.jackson.map.ObjectWriter;
-import org.eclipse.jetty.servlet.FilterHolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeClass;
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 
-
-
 import com.ning.billing.jaxrs.json.AccountJson;
-import com.ning.billing.jaxrs.json.SubscriptionJson;
-import com.ning.billing.server.listeners.KillbillGuiceListener;
-import com.ning.http.client.AsyncCompletionHandler;
-import com.ning.http.client.AsyncHttpClient;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.http.client.Response;
-import com.ning.jetty.core.CoreConfig;
-import com.ning.jetty.core.server.HttpServer;
 
-public class TestAccount {
 
-	private static final Logger log = LoggerFactory.getLogger(TestAccount.class);
+public class TestAccount extends TestJaxrsBase {
 
-	public static final String HEADER_CONTENT_TYPE = "Content-type";
-	public static final String CONTENT_TYPE = "application/json";
-
-	private ObjectMapper mapper;
-
-	private HttpServer server;
-	private AsyncHttpClient httpClient;
-	private CoreConfig config;
-
-	CoreConfig getConfig() {
-		return new CoreConfig() {
-
-			@Override
-			public boolean isSSLEnabled() {
-				return false;
-			}
-			@Override
-			public boolean isJettyStatsOn() {
-				return false;
-			}
-			@Override
-			public int getServerSslPort() {
-				return 0;
-			}
-			@Override
-			public int getServerPort() {
-				return 8080;
-			}
-			@Override
-			public String getServerHost() {
-				return "127.0.0.1";
-			}
-			@Override
-			public String getSSLkeystorePassword() {
-				return null;
-			}
-			@Override
-			public String getSSLkeystoreLocation() {
-				return null;
-			}
-			@Override
-			public int getMinThreads() {
-				return 2;
-			}
-			@Override
-			public int getMaxThreads() {
-				return 100;
-			}
-			@Override
-			public String getLogPath() {
-				return "/var/tmp/.logs";
-			}
-		};
-	}
-
-	  public static void loadSystemPropertiesFromClasspath(final String resource) {
-	        final URL url = TestAccount.class.getResource(resource);
-	        assertNotNull(url);
-
-	        try {
-	            System.getProperties().load( url.openStream() );
-	        } catch (IOException e) {
-	            throw new RuntimeException(e);
-	        }
-	    }
-
-
-	@BeforeClass(groups="slow")
-	public void setup() throws Exception {
-
-		loadSystemPropertiesFromClasspath("/killbill.properties");
-
-		httpClient = new AsyncHttpClient();
-		server = new HttpServer();
-		config = getConfig();
-		mapper = new ObjectMapper();
-		final Iterable<EventListener> eventListeners = new Iterable<EventListener>() {
-			@Override
-			public Iterator<EventListener> iterator() {
-				ArrayList<EventListener> array = new ArrayList<EventListener>();
-				array.add(new KillbillGuiceListener());
-				return array.iterator();
-			}
-		};
-		server.configure(config, eventListeners, new HashMap<FilterHolder, String>());
-		server.start();
-	}
+	private static final Logger log = LoggerFactory.getLogger(TestAccount.class);
 
 
-	AccountJson getAccountJson() {
-		String accountId = UUID.randomUUID().toString();
-		String name = "yoyo bozo2";
-		int length = 4;
-		String externalKey = "xdfsdretuq";
-		String email = "yoyo@gmail.com";
-		int billCycleDay = 12;
-		String currency = "USD";
-		String paymentProvider = "paypal";
-		String timeZone = "UTC";
-		String address1 = "12 rue des ecoles";
-		String address2 = "Poitier";
-		String company = "Renault";
-		String state = "Poitou";
-		String country = "France";
-		String phone = "81 53 26 56";
-
-		AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, paymentProvider, timeZone, address1, address2, company, state, country, phone);
-		return accountJson;
-	}
-
-	@Test(groups="slow", enabled=false)
-	public void testFoo() throws Exception {
+	@Test(groups="slow", enabled=true)
+	public void testAccountOk() throws Exception {
 		
-		ObjectMapper mapper = new ObjectMapper();
+		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));
+
+		// 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);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		baseJson = response.getResponseBody();
+		objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertTrue(objFromJson.equalsNoId(input));
 		
-		AccountJson accountData = getAccountJson();
-
-		  ObjectWriter objWriter = mapper.writer();
-
-          Writer writer = new StringWriter();
-          objWriter.writeValue(writer, accountData);
-          String baseJson = writer.toString();
-
-          log.info(baseJson);
+		// Update Account
+		AccountJson newInput = new AccountJson(objFromJson.getAcountId(),
+				"zozo", 4, objFromJson.getExternalKey(), "rr@google.com", 18, "EUR", "none", "UTC", "bl1", "bh2", "", "ca", "usa", "415-255-2991");
+		baseJson = mapper.writeValueAsString(newInput);
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + objFromJson.getAcountId();
+		response = doPut(uri, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+		baseJson = response.getResponseBody();
+		objFromJson = mapper.readValue(baseJson, AccountJson.class);
+		Assert.assertTrue(objFromJson.equals(newInput));
+	}
 
-          AccountJson objFromJson = mapper.readValue(baseJson, AccountJson.class);
 
-          log.info(objFromJson.toString());
+	@Test(groups="slow", enabled=true)
+	public void testUpdateNonExistentAccount() throws Exception {
+		AccountJson input = getAccountJson("xoxo", "shdgfhwe", "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);
+		Assert.assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+		String body = response.getResponseBody();
+		Assert.assertEquals(body, "");
 	}
 	
 	
-	@Test(groups="slow", enabled=false)
-	public void testAccountOk() throws Exception {
-
-		final String accountPathPrefix = "/1.0/account";
-
-		AccountJson accountData = getAccountJson();
-		ObjectWriter objWriter = mapper.writer();
-
-        Writer writer = new StringWriter();
-        objWriter.writeValue(writer, accountData);
-        String baseJson = writer.toString();
-        
-        try {
-        	Thread.sleep(100000);
-        } catch (Exception e) {}
-        
-		httpClient.preparePost(String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), accountPathPrefix))
-		.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE)
-		.setBody(baseJson)
-		.execute(new AsyncCompletionHandler<Integer>() {
-
-			@Override
-			public Integer onCompleted(Response response)
-			throws Exception {
-
-				int statusCode = response.getStatusCode();
-				URI uri = response.getUri();
-				return statusCode;
-			}
-		});
+	@Test(groups="slow", enabled=true)
+	public void testAccountNonExistent() throws Exception {
+		final String uri = BaseJaxrsResource.ACCOUNTS_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());
+	}
+	
+	@Test(groups="slow", enabled=true)
+	public void testAccountBadAccountId() throws Exception {
+		final String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/yo";
+		Response response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+		Assert.assertEquals(response.getStatusCode(), Status.NOT_FOUND.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
new file mode 100644
index 0000000..4db4a3b
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -0,0 +1,291 @@
+/* 
+ * 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.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+import com.google.inject.Injector;
+import com.google.inject.Module;
+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.server.listeners.KillbillGuiceListener;
+import com.ning.billing.server.modules.KillbillServerModule;
+import com.ning.http.client.AsyncCompletionHandler;
+import com.ning.http.client.AsyncHttpClient;
+import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
+import com.ning.http.client.ListenableFuture;
+import com.ning.http.client.Response;
+import com.ning.jetty.core.CoreConfig;
+import com.ning.jetty.core.server.HttpServer;
+
+public class TestJaxrsBase {
+	
+	protected static final int DEFAULT_HTTP_TIMEOUT_SEC = 5;
+	
+	protected static final Map<String, String> DEFAULT_EMPTY_QUERY = new HashMap<String, String>();
+	
+	private static final Logger log = LoggerFactory.getLogger(TestJaxrsBase.class);
+
+	public static final String HEADER_CONTENT_TYPE = "Content-type";
+	public static final String CONTENT_TYPE = "application/json";
+
+	private MysqlTestingHelper helper;
+	private HttpServer server;
+
+	protected CoreConfig config;
+	protected AsyncHttpClient httpClient;	
+	protected ObjectMapper mapper;
+
+
+	public static void loadSystemPropertiesFromClasspath(final String resource) {
+		final URL url = TestJaxrsBase.class.getResource(resource);
+		assertNotNull(url);
+		try {
+			System.getProperties().load( url.openStream() );
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	public static class TestKillbillGuiceListener extends KillbillGuiceListener {
+		public TestKillbillGuiceListener() {
+			super();
+		}
+		@Override
+		protected Module getModule() {
+	    	return new TestKillbillServerModule();
+	    }
+		public Injector getTheInjector() {
+			return theInjector;
+		}
+	}
+
+	public static class TestKillbillServerModule extends KillbillServerModule {
+		
+		@Override
+		protected void configureDao() {
+			final MysqlTestingHelper helper = new MysqlTestingHelper();
+			bind(MysqlTestingHelper.class).toInstance(helper);
+			if (helper.isUsingLocalInstance()) {
+				bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+				final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+				bind(DbiConfig.class).toInstance(config);
+			} else {
+				final IDBI dbi = helper.getDBI();
+				bind(IDBI.class).toInstance(dbi);
+			}
+	    }
+	}
+
+
+	
+	@BeforeClass(groups="slow")
+	public void setup() throws Exception {
+
+		loadSystemPropertiesFromClasspath("/killbill.properties");
+
+		final EventListener eventListener = new TestKillbillGuiceListener();
+		httpClient = new AsyncHttpClient();
+		server = new HttpServer();
+		config = getConfig();
+		mapper = new ObjectMapper();
+		final Iterable<EventListener> eventListeners = new Iterable<EventListener>() {
+			@Override
+			public Iterator<EventListener> iterator() {
+				ArrayList<EventListener> array = new ArrayList<EventListener>();
+				array.add(eventListener);
+				return array.iterator();
+			}
+		};
+		server.configure(config, eventListeners, new HashMap<FilterHolder, String>());
+		server.start();
+		
+		Injector injector = ((TestKillbillGuiceListener) eventListener).getTheInjector();
+		
+		helper = injector.getInstance(MysqlTestingHelper.class);
+		helper.cleanupAllTables();
+	}
+	
+	@AfterClass(groups="slow")
+	public void tearDown() {
+		if (helper != null) {
+			try {
+				helper.startMysql();
+			} catch (IOException ignore) {
+			}
+		}
+	}
+
+	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) {
+			builder.setBody(body);
+		}
+		return executeAndWait(builder, timeoutSec);
+	}
+
+	protected Response doPut(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
+		final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+		BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("PUT", url, queryParams);
+		if (body != null) {
+			builder.setBody(body);
+		}
+		return executeAndWait(builder, timeoutSec);
+	}
+	
+	protected Response doDelete(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+		final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+		BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("DELETE", url, queryParams);
+		return executeAndWait(builder, timeoutSec);
+	}
+
+	protected Response doGet(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
+		final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+		return doGetWithUrl(url, queryParams, timeoutSec);
+	}
+	
+	protected Response doGetWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
+		BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("GET", url, queryParams);
+		return executeAndWait(builder, timeoutSec);
+	}
+	
+	private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec) {
+		Response response = null;
+		try {
+			ListenableFuture<Response> futureStatus = 
+			builder.execute(new AsyncCompletionHandler<Response>() {
+				@Override
+				public Response onCompleted(Response response) throws Exception {
+					return response;
+				}
+			});
+			response = futureStatus.get(timeoutSec, TimeUnit.SECONDS);
+		} catch (Exception e) {
+			Assert.fail(e.getMessage());			
+		}
+		Assert.assertNotNull(response);
+		return response;
+	}
+	
+	private String getUrlFromUri(final String uri) {
+		return String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
+	}
+	
+	private BoundRequestBuilder getBuilderWithHeaderAndQuery(final String verb, final String url, final Map<String, String> queryParams) {
+		BoundRequestBuilder builder = null;
+		if (verb.equals("GET")) {
+			builder = httpClient.prepareGet(url);
+		} else if (verb.equals("POST")) {
+			builder = httpClient.preparePost(url);
+		} else if (verb.equals("PUT")) {
+			builder = httpClient.preparePut(url);			
+		} else if (verb.equals("DELETE")) {
+			builder = httpClient.prepareDelete(url);			
+		}
+		builder.addHeader(HEADER_CONTENT_TYPE, CONTENT_TYPE);
+		for (Entry<String, String> q : queryParams.entrySet()) {
+			builder.addQueryParameter(q.getKey(), q.getValue());
+		}
+		return builder;
+	}
+	
+	public AccountJson getAccountJson(final String name, final String externalKey, final String email) {
+		String accountId = UUID.randomUUID().toString();
+		int length = 4;
+		int billCycleDay = 12;
+		String currency = "USD";
+		String paymentProvider = "paypal";
+		String timeZone = "UTC";
+		String address1 = "12 rue des ecoles";
+		String address2 = "Poitier";
+		String company = "Renault";
+		String state = "Poitou";
+		String country = "France";
+		String phone = "81 53 26 56";
+
+		AccountJson accountJson = new AccountJson(accountId, name, length, externalKey, email, billCycleDay, currency, paymentProvider, timeZone, address1, address2, company, state, country, phone);
+		return accountJson;
+	}
+
+	
+	private CoreConfig getConfig() {
+		return new CoreConfig() {
+			@Override
+			public boolean isSSLEnabled() {
+				return false;
+			}
+			@Override
+			public boolean isJettyStatsOn() {
+				return false;
+			}
+			@Override
+			public int getServerSslPort() {
+				return 0;
+			}
+			@Override
+			public int getServerPort() {
+				return 8080;
+			}
+			@Override
+			public String getServerHost() {
+				return "127.0.0.1";
+			}
+			@Override
+			public String getSSLkeystorePassword() {
+				return null;
+			}
+			@Override
+			public String getSSLkeystoreLocation() {
+				return null;
+			}
+			@Override
+			public int getMinThreads() {
+				return 2;
+			}
+			@Override
+			public int getMaxThreads() {
+				return 100;
+			}
+			@Override
+			public String getLogPath() {
+				return "/var/tmp/.logs";
+			}
+		};
+	}
+}
diff --git a/server/src/test/resources/killbill.properties b/server/src/test/resources/killbill.properties
index 251d154..c3fe203 100644
--- a/server/src/test/resources/killbill.properties
+++ b/server/src/test/resources/killbill.properties
@@ -1,4 +1,5 @@
-com.ning.jetty.jdbi.url=jdbc:mysql://127.0.0.1:3306/killbill
+# Use killbill util test properties (DbiProvider/MysqltestingHelper) on the test side configured with test_killbill
+com.ning.billing.dbi.jdbc.url=jdbc:mysql://127.0.0.1:3306/test_killbill
 
 killbill.catalog.uri=file:src/test/resources/catalog-weapons.xml
 
@@ -8,4 +9,7 @@ killbill.payment.retry.days=8,8,8
 
 user.timezone=UTC
 
+com.ning.billing.dbi.test.useLocalDb=true
+
+
 
diff --git a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
index 866289e..9995229 100644
--- a/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
+++ b/util/src/test/java/com/ning/billing/dbi/MysqlTestingHelper.java
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.io.FileUtils;
@@ -27,6 +28,7 @@ import org.skife.jdbi.v2.DBI;
 import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
 import org.skife.jdbi.v2.tweak.HandleCallback;
+import org.skife.jdbi.v2.util.StringMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -48,6 +50,8 @@ public class MysqlTestingHelper
     private static final String USERNAME = "root";
     private static final String PASSWORD = "root";
 
+    // Discover dynamically list of all tables in that database;
+    private List<String> allTables;    
     private File dbDir;
     private MysqldResource mysqldResource;
     private int port;
@@ -122,7 +126,36 @@ public class MysqlTestingHelper
             }
         });
     }
+    
+    public void cleanupAllTables() {
+    	final List<String> tablesToCleanup = fetchAllTables();
+    	for (String tableName : tablesToCleanup) {
+    		cleanupTable(tableName);
+    	}
+    }
+
+    public synchronized List<String> fetchAllTables() {
+
+    	if (allTables == null) {
+    		final String dbiString = "jdbc:mysql://localhost:" + port + "/information_schema";
+    		IDBI cleanupDbi = new DBI(dbiString, USERNAME, PASSWORD);
+
+    		final List<String> tables=  cleanupDbi.withHandle(new HandleCallback<List<String>>() {
+
+    			@Override
+    			public List<String> withHandle(Handle h) throws Exception {
+    				return h.createQuery("select table_name from tables where table_schema = :table_schema and table_type = 'BASE TABLE';")
+    				.bind("table_schema", DB_NAME)
+    				.map(new StringMapper())
+    				.list();
+    			}
+    		});
+    		allTables = tables;
+    	}
+    	return allTables;
+    }
 
+    
     public void stopMysql()
     {
         if (mysqldResource != null) {