Merge branch 'master' of github.com:jclouds/jclouds into 1.5.x

* 'master' of github.com:jclouds/jclouds:
  Issue 915: don't create full path on useradd
  Issue 889: fixes error msg on aborting early wait for node-running.
  update sshj to 0.8.0, fixing power-of-2 bug reported by me and fixed by aled
  Issue 926: cleaned up usage of authentication client
  removed awkward test, as this is now addressed at a higher level. Ex. malformed date string creation can be tested in jclouds-core
  Issue 647: normalized use of IllegalArgumentException
  Added a 'clear container' admin function to cf-tweetstore-spring
  Added a 'clear container' admin function to runatcloud-tweetstore
  Added a 'clear container' admin function to rhcloud-tweetstore
  Added a 'clear container' admin function to gae-tweetstore-spring
  Added a 'clear container' admin function to gae-tweetstore
diff --git a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationAsyncClient.java b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationAsyncClient.java
index aaa1c6f..c8b5152 100644
--- a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationAsyncClient.java
+++ b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationAsyncClient.java
@@ -46,18 +46,29 @@
 public interface AuthenticationAsyncClient {
 
    /**
-    * @see AuthenticationClient#authenticateTenantWithCredentials(String,PasswordCredentials)
+    * @see AuthenticationClient#authenticateWithTenantNameAndCredentials(String,PasswordCredentials)
     */
    @POST
    @SelectJson("access")
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/tokens")
    @MapBinder(BindAuthToJsonPayload.class)
-   ListenableFuture<Access> authenticateTenantWithCredentials(@PayloadParam("tenantName") String tenantId,
+   ListenableFuture<Access> authenticateWithTenantNameAndCredentials(@PayloadParam("tenantName") String tenantName,
+            PasswordCredentials passwordCredentials);
+   
+   /**
+    * @see AuthenticationClient#authenticateWithTenantIdAndCredentials(String,PasswordCredentials)
+    */
+   @POST
+   @SelectJson("access")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Path("/tokens")
+   @MapBinder(BindAuthToJsonPayload.class)
+   ListenableFuture<Access> authenticateWithTenantIdAndCredentials(@PayloadParam("tenantId") String tenantId,
             PasswordCredentials passwordCredentials);
 
    /**
-    * @see AuthenticationClient#authenticateTenantWithCredentials(String,ApiAccessKeyCredentials)
+    * @see AuthenticationClient#authenticateWithTenantNameAndCredentials(String,ApiAccessKeyCredentials)
     */
    @POST
    @SelectJson("access")
@@ -66,17 +77,17 @@
    @MapBinder(BindAuthToJsonPayload.class)
    // TODO: is tenantName permanent? or should we switch to tenantId at some point. seems most tools
    // still use tenantName
-   ListenableFuture<Access> authenticateTenantWithCredentials(@PayloadParam("tenantName") String tenantId,
+   ListenableFuture<Access> authenticateWithTenantNameAndCredentials(@PayloadParam("tenantName") String tenantName,
             ApiAccessKeyCredentials apiAccessKeyCredentials);
    
    /**
-    * @see AuthenticationClient#authenticateTenantWithCredentials(String,ApiAccessKeyCredentials)
+    * @see AuthenticationClient#authenticateWithTenantIdAndCredentials(String,ApiAccessKeyCredentials)
     */
    @POST
    @SelectJson("access")
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/tokens")
    @MapBinder(BindAuthToJsonPayload.class)
-   ListenableFuture<Access> authenticateTenantWithTenantIdAndCredentials(@PayloadParam("tenantId") String tenantId,
+   ListenableFuture<Access> authenticateWithTenantIdAndCredentials(@PayloadParam("tenantId") String tenantId,
             ApiAccessKeyCredentials apiAccessKeyCredentials);
 }
diff --git a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationClient.java b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationClient.java
index 2d8c042..167f6bb 100644
--- a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationClient.java
+++ b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/AuthenticationClient.java
@@ -42,19 +42,26 @@
     * 
     * @return access with token
     */
-   Access authenticateTenantWithCredentials(String tenantId, PasswordCredentials passwordCredentials);
+   Access authenticateWithTenantNameAndCredentials(String tenantId, PasswordCredentials passwordCredentials);
 
    /**
     * Authenticate to generate a token.
     * 
     * @return access with token
     */
-   Access authenticateTenantWithCredentials(String tenantId, ApiAccessKeyCredentials passwordCredentials);
+   Access authenticateWithTenantIdAndCredentials(String tenantId, PasswordCredentials passwordCredentials);
+
+   /**
+    * Authenticate to generate a token.
+    * 
+    * @return access with token
+    */
+   Access authenticateWithTenantNameAndCredentials(String tenantId, ApiAccessKeyCredentials passwordCredentials);
    
    /**
     * Authenticate to generate a token.
     * 
     * @return access with token
     */
-   Access authenticateTenantWithTenantIdAndCredentials(String tenantId, ApiAccessKeyCredentials passwordCredentials);
+   Access authenticateWithTenantIdAndCredentials(String tenantId, ApiAccessKeyCredentials passwordCredentials);
 }
\ No newline at end of file
diff --git a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticateApiAccessKeyCredentials.java b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticateApiAccessKeyCredentials.java
index 035be75..d1aa97f 100644
--- a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticateApiAccessKeyCredentials.java
+++ b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticateApiAccessKeyCredentials.java
@@ -42,7 +42,7 @@
    public Access apply(Credentials input) {
       if (input.identity.indexOf(':') == -1) {
          throw new AuthorizationException(String.format("Identity %s does not match format tenantName:accessKey",
-                  input.identity), null);
+               input.identity), null);
       }
 
       String tenantId = input.identity.substring(0, input.identity.indexOf(':'));
@@ -50,12 +50,12 @@
       String passwordOrSecretKey = input.credential;
 
       ApiAccessKeyCredentials apiAccessKeyCredentials = ApiAccessKeyCredentials.createWithAccessKeyAndSecretKey(
-               usernameOrAccessKey, passwordOrSecretKey);
+            usernameOrAccessKey, passwordOrSecretKey);
       Access access;
-      if(tenantId.matches("[0-9]*")) {
-    	  access = client.authenticateTenantWithTenantIdAndCredentials(tenantId, apiAccessKeyCredentials);
+      if (tenantId.matches("^[0-9]+$")) {
+         access = client.authenticateWithTenantIdAndCredentials(tenantId, apiAccessKeyCredentials);
       } else {
-    	  access = client.authenticateTenantWithCredentials(tenantId, apiAccessKeyCredentials);
+         access = client.authenticateWithTenantNameAndCredentials(tenantId, apiAccessKeyCredentials);
       }
       return access;
    }
diff --git a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticatePasswordCredentials.java b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticatePasswordCredentials.java
index 0c59e02..aa997aa 100644
--- a/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticatePasswordCredentials.java
+++ b/common/openstack/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/AuthenticatePasswordCredentials.java
@@ -17,6 +17,7 @@
  * under the License.
  */
 package org.jclouds.openstack.keystone.v2_0.functions;
+
 import javax.inject.Inject;
 
 import org.jclouds.domain.Credentials;
@@ -39,16 +40,22 @@
    public Access apply(Credentials input) {
       if (input.identity.indexOf(':') == -1) {
          throw new AuthorizationException(String.format("Identity %s does not match format tenantId:username",
-                  input.identity), null);
+               input.identity), null);
       }
-      
+
       String tenantId = input.identity.substring(0, input.identity.indexOf(':'));
       String usernameOrAccessKey = input.identity.substring(input.identity.indexOf(':') + 1);
       String passwordOrSecretKey = input.credential;
 
       PasswordCredentials passwordCredentials = PasswordCredentials.createWithUsernameAndPassword(usernameOrAccessKey,
-               passwordOrSecretKey);
-      return client.authenticateTenantWithCredentials(tenantId, passwordCredentials);
+            passwordOrSecretKey);
+      Access access;
+      if (tenantId.matches("^[0-9]+$")) {
+         access = client.authenticateWithTenantIdAndCredentials(tenantId, passwordCredentials);
+      } else {
+         access = client.authenticateWithTenantNameAndCredentials(tenantId, passwordCredentials);
+      }
+      return access;
    }
 
    @Override
diff --git a/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java b/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java
index 38612f5..67f48be 100644
--- a/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java
+++ b/compute/src/main/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.java
@@ -47,6 +47,7 @@
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.Multimap;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
@@ -127,16 +128,26 @@
       try {
          if (options.shouldBlockUntilRunning()) {
             try {
+               Stopwatch stopwatch = new Stopwatch().start();
                if (!nodeRunning.apply(node)) {
+                  long timeWaited = stopwatch.elapsedMillis();
+                  long earlyReturnGrace = 10; // sleeps can sometimes return milliseconds early
+                  
                   if (node.get() == null) {
                      node.set(originalNode);
                      throw new IllegalStateException(format("api response for node(%s) was null, so we can't customize",
                            originalId));
+                  } else if (timeWaited < (timeouts.nodeRunning - earlyReturnGrace)) {
+                     throw new IllegalStateException(
+                           format(
+                                 "node(%s) didn't achieve the state running, so we couldn't customize; aborting prematurely after %d seconds with final state: %s",
+                                 originalId, timeWaited / 1000, node.get().getState()));
+                  } else {
+                     throw new IllegalStateException(
+                           format(
+                                 "node(%s) didn't achieve the state running within %d seconds, so we couldn't customize; final state: %s",
+                                 originalId, timeouts.nodeRunning / 1000, node.get().getState()));
                   }
-                  throw new IllegalStateException(
-                        format(
-                              "node(%s) didn't achieve the state running within %d seconds, so we couldn't customize; final state: %s",
-                              originalId, timeouts.nodeRunning / 1000, node.get().getState()));
                }
             } catch (IllegalStateException e) {
                if (node.get().getState() == NodeState.TERMINATED) {
diff --git a/compute/src/test/java/org/jclouds/compute/callables/RunScriptOnNodeUsingSshTest.java b/compute/src/test/java/org/jclouds/compute/callables/RunScriptOnNodeUsingSshTest.java
index 7030c05..2a6bed2 100644
--- a/compute/src/test/java/org/jclouds/compute/callables/RunScriptOnNodeUsingSshTest.java
+++ b/compute/src/test/java/org/jclouds/compute/callables/RunScriptOnNodeUsingSshTest.java
@@ -126,7 +126,7 @@
       expect(sshClient.getUsername()).andReturn("tester");
       expect(sshClient.getHostAddress()).andReturn("somewhere.example.com");
       expect(
-            sshClient.exec("sudo sh <<'RUN_SCRIPT_AS_ROOT_SSH'\n" + "mkdir -p /home/users/testuser\n"
+            sshClient.exec("sudo sh <<'RUN_SCRIPT_AS_ROOT_SSH'\n" + "mkdir -p /home/users\n"
                   + "useradd -s /bin/bash -m  -d /home/users/testuser testuser\n"
                   + "chown -R testuser /home/users/testuser\n" + "RUN_SCRIPT_AS_ROOT_SSH\n")).andReturn(
             new ExecResponse("done", null, 0));
diff --git a/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java b/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java
index 9e3db80..9b36d3a 100644
--- a/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java
+++ b/compute/src/test/java/org/jclouds/compute/strategy/CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapTest.java
@@ -23,6 +23,7 @@
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
 
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -91,8 +92,9 @@
       
       assertEquals(goodNodes.size(), 0);
       assertEquals(badNodes.keySet(), ImmutableSet.of(node));
-      assertEquals(badNodes.get(node).getMessage(),
-               "node(id) didn't achieve the state running within 1200 seconds, so we couldn't customize; final state: PENDING");
+      assertTrue(badNodes.get(node).getMessage() != null && badNodes.get(node).getMessage().matches(
+               "node\\(id\\) didn't achieve the state running, so we couldn't customize; aborting prematurely after .* seconds with final state: PENDING"),
+               badNodes.get(node).getMessage());
       assertEquals(customizationResponses.size(), 0);
 
       // verify mocks
diff --git a/compute/src/test/resources/initscript_with_java.sh b/compute/src/test/resources/initscript_with_java.sh
index f4220a0..093c4c2 100644
--- a/compute/src/test/resources/initscript_with_java.sh
+++ b/compute/src/test/resources/initscript_with_java.sh
@@ -182,7 +182,7 @@
 		%wheel ALL = (ALL) NOPASSWD:ALL
 	END_OF_JCLOUDS_FILE
 	chmod 0440 /etc/sudoers
-	mkdir -p /home/users/defaultAdminUsername
+	mkdir -p /home/users
 	groupadd -f wheel
 	useradd -s /bin/bash -g wheel -m  -d /home/users/defaultAdminUsername -p 'crypt(randompassword)' defaultAdminUsername
 	mkdir -p /home/users/defaultAdminUsername/.ssh
diff --git a/compute/src/test/resources/initscript_with_jboss.sh b/compute/src/test/resources/initscript_with_jboss.sh
index 031c842..f9bd795 100644
--- a/compute/src/test/resources/initscript_with_jboss.sh
+++ b/compute/src/test/resources/initscript_with_jboss.sh
@@ -182,7 +182,7 @@
 		%wheel ALL = (ALL) NOPASSWD:ALL
 	END_OF_JCLOUDS_FILE
 	chmod 0440 /etc/sudoers
-	mkdir -p /home/users/web
+	mkdir -p /home/users
 	groupadd -f wheel
 	useradd -s /bin/bash -g wheel -m  -d /home/users/web -p 'crypt(randompassword)' web
 	mkdir -p /home/users/web/.ssh
diff --git a/compute/src/test/resources/runscript_adminUpdate.sh b/compute/src/test/resources/runscript_adminUpdate.sh
index 802e808..e2f9a53 100644
--- a/compute/src/test/resources/runscript_adminUpdate.sh
+++ b/compute/src/test/resources/runscript_adminUpdate.sh
@@ -89,7 +89,7 @@
 		%wheel ALL = (ALL) NOPASSWD:ALL
 	END_OF_JCLOUDS_FILE
 	chmod 0440 /etc/sudoers
-	mkdir -p /over/ridden/foo
+	mkdir -p /over/ridden
 	groupadd -f wheel
 	useradd -s /bin/bash -g wheel -m  -d /over/ridden/foo -p 'crypt(randompassword)' foo
 	mkdir -p /over/ridden/foo/.ssh
diff --git a/core/src/main/java/org/jclouds/date/DateCodec.java b/core/src/main/java/org/jclouds/date/DateCodec.java
index 9e31161..8570b58 100644
--- a/core/src/main/java/org/jclouds/date/DateCodec.java
+++ b/core/src/main/java/org/jclouds/date/DateCodec.java
@@ -1,12 +1,41 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.jclouds.date;
 
-import java.text.ParseException;
 import java.util.Date;
 
+/**
+ * converting from Date->String and vice versa.
+ * 
+ * @author aled
+ */
 public interface DateCodec {
 
-   public Date toDate(String date) throws ParseException;
-   
+   /**
+    * @param toParse
+    *           text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException
+    *            if the input is invalid
+    */
+   public Date toDate(String date) throws IllegalArgumentException;
+
    public String toString(Date date);
-   
+
 }
diff --git a/core/src/main/java/org/jclouds/date/DateCodecFactory.java b/core/src/main/java/org/jclouds/date/DateCodecFactory.java
index 4dd0b97..0087298 100644
--- a/core/src/main/java/org/jclouds/date/DateCodecFactory.java
+++ b/core/src/main/java/org/jclouds/date/DateCodecFactory.java
@@ -1,6 +1,24 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 package org.jclouds.date;
 
-import org.jclouds.date.internal.SimpleDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory;
 
 import com.google.inject.ImplementedBy;
 
@@ -10,7 +28,7 @@
  * 
  * @author aled
  */
-@ImplementedBy(SimpleDateCodecFactory.class)
+@ImplementedBy(DateServiceDateCodecFactory.class)
 public interface DateCodecFactory {
 
    public DateCodec rfc1123();
diff --git a/core/src/main/java/org/jclouds/date/DateService.java b/core/src/main/java/org/jclouds/date/DateService.java
index a8c0e2d..75bfd55 100644
--- a/core/src/main/java/org/jclouds/date/DateService.java
+++ b/core/src/main/java/org/jclouds/date/DateService.java
@@ -40,14 +40,24 @@
 
    String cDateFormat();
 
-   Date cDateParse(String toParse);
+   /**
+    * @param toParse text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException if the input is invalid
+    */
+   Date cDateParse(String toParse) throws IllegalArgumentException;
 
    String rfc822DateFormat(Date date);
 
    String rfc822DateFormat();
-
-   Date rfc822DateParse(String toParse);
-
+   
+   /**
+    * @param toParse text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException if the input is invalid
+    */
+   Date rfc822DateParse(String toParse) throws IllegalArgumentException;
+   
    String iso8601SecondsDateFormat(Date dateTime);
 
    String iso8601SecondsDateFormat();
@@ -55,15 +65,30 @@
    String iso8601DateFormat(Date date);
 
    String iso8601DateFormat();
+   
+   /**
+    * @param toParse text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException if the input is invalid
+    */
+   Date iso8601DateParse(String toParse) throws IllegalArgumentException;
 
-   Date iso8601DateParse(String toParse);
-
-   Date iso8601SecondsDateParse(String toParse);
+   /**
+    * @param toParse text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException if the input is invalid
+    */
+   Date iso8601SecondsDateParse(String toParse) throws IllegalArgumentException;
 
    String rfc1123DateFormat(Date date);
 
    String rfc1123DateFormat();
 
-   Date rfc1123DateParse(String toParse);
+   /**
+    * @param toParse text to parse
+    * @return parsed date
+    * @throws IllegalArgumentException if the input is invalid
+    */
+   Date rfc1123DateParse(String toParse) throws IllegalArgumentException;
 
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java b/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java
new file mode 100644
index 0000000..62f54f6
--- /dev/null
+++ b/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.date.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+
+import javax.inject.Singleton;
+
+import org.jclouds.date.DateCodec;
+import org.jclouds.date.DateCodecFactory;
+import org.jclouds.date.DateService;
+
+import com.google.inject.Inject;
+
+@Singleton
+public class DateServiceDateCodecFactory implements DateCodecFactory {
+
+   private final DateCodec rfc1123Codec;
+
+   @Inject
+   public DateServiceDateCodecFactory(DateServiceRfc1123Codec rfc1123Codec) {
+      this.rfc1123Codec = checkNotNull(rfc1123Codec, "rfc1123Codec");
+   }
+
+   @Singleton
+   public static class DateServiceRfc1123Codec implements DateCodec {
+
+      private final DateService dateService;
+
+      @Inject
+      public DateServiceRfc1123Codec(final DateService dateService) {
+         this.dateService = checkNotNull(dateService, "dateService");
+      }
+
+      @Override
+      public Date toDate(String date) throws IllegalArgumentException {
+         return dateService.rfc1123DateParse(date);
+      }
+
+      @Override
+      public String toString(Date date) {
+         return dateService.rfc1123DateFormat(date);
+      }
+
+      @Override
+      public String toString() {
+         return "rfc1123Codec [dateService=" + dateService + "]";
+      }
+
+   }
+
+   public DateCodec rfc1123() {
+      return rfc1123Codec;
+   }
+  
+}
diff --git a/core/src/main/java/org/jclouds/date/internal/SimpleDateCodecFactory.java b/core/src/main/java/org/jclouds/date/internal/SimpleDateCodecFactory.java
deleted file mode 100644
index 20a0b78..0000000
--- a/core/src/main/java/org/jclouds/date/internal/SimpleDateCodecFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.jclouds.date.internal;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.text.ParseException;
-import java.util.Date;
-
-import org.jclouds.date.DateCodec;
-import org.jclouds.date.DateCodecFactory;
-import org.jclouds.date.DateService;
-
-import com.google.inject.Inject;
-
-public class SimpleDateCodecFactory implements DateCodecFactory {
-
-   private final DateService dateService;
-
-   private volatile DateCodec rfc1123Codec;
-   
-   @Inject
-   public SimpleDateCodecFactory(final DateService dateService) {
-      this.dateService = checkNotNull(dateService, "dateService");
-   }
-
-   public DateCodec rfc1123() {
-      if (rfc1123Codec == null) {
-         rfc1123Codec = new DateCodec() {
-            @Override
-            public Date toDate(String date) throws ParseException {
-               return dateService.rfc1123DateParse(date);
-            }
-   
-            @Override
-            public String toString(Date date) {
-               return dateService.rfc1123DateFormat(date);
-            }
-         };
-      }
-      return rfc1123Codec;
-   }
-}
diff --git a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java
index bcff3f0..dd96f2a 100644
--- a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java
+++ b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java
@@ -89,7 +89,7 @@
          try {
             return cSimpleDateFormat.parse(toParse);
          } catch (ParseException pe) {
-            throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
+            throw new IllegalArgumentException("Error parsing data at " + pe.getErrorOffset(), pe);
          }
       }
    }
@@ -112,7 +112,7 @@
          try {
             return rfc822SimpleDateFormat.parse(toParse);
          } catch (ParseException pe) {
-            throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
+            throw new IllegalArgumentException("Error parsing data at " + pe.getErrorOffset(), pe);
          }
       }
    }
@@ -141,6 +141,8 @@
 
    @Override
    public final Date iso8601DateParse(String toParse) {
+      if (toParse.length() < 10)
+         throw new IllegalArgumentException("incorrect date format " + toParse);
       String tz = findTZ(toParse);
       toParse = trimToMillis(toParse);
       toParse = trimTZ(toParse);
@@ -151,13 +153,15 @@
          try {
             return iso8601SimpleDateFormat.parse(toParse);
          } catch (ParseException pe) {
-            throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
+            throw new IllegalArgumentException("Error parsing data at " + pe.getErrorOffset(), pe);
          }
       }
    }
 
    @Override
    public final Date iso8601SecondsDateParse(String toParse) {
+      if (toParse.length() < 10)
+         throw new IllegalArgumentException("incorrect date format " + toParse);
       String tz = findTZ(toParse);
       toParse = trimToMillis(toParse);
       toParse = trimTZ(toParse);
@@ -168,7 +172,7 @@
          try {
             return iso8601SecondsSimpleDateFormat.parse(toParse);
          } catch (ParseException pe) {
-            throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
+            throw new IllegalArgumentException("Error parsing data at " + pe.getErrorOffset(), pe);
          }
       }
    }
@@ -198,12 +202,12 @@
    }
 
    @Override
-   public final Date rfc1123DateParse(String toParse) {
+   public final Date rfc1123DateParse(String toParse) throws IllegalArgumentException {
       synchronized (rfc1123SimpleDateFormat) {
          try {
             return rfc1123SimpleDateFormat.parse(toParse);
          } catch (ParseException pe) {
-            throw new RuntimeException("Error parsing data at " + pe.getErrorOffset(), pe);
+            throw new IllegalArgumentException("Error parsing data at " + pe.getErrorOffset(), pe);
          }
       }
    }
diff --git a/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java
index 4d2c801..64faacf 100644
--- a/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java
+++ b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java
@@ -5,7 +5,6 @@
 import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
 import static javax.ws.rs.core.HttpHeaders.EXPIRES;
 
-import java.text.ParseException;
 import java.util.Date;
 import java.util.Map.Entry;
 
@@ -17,10 +16,8 @@
 import org.jclouds.date.DateCodecFactory;
 import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
 import org.jclouds.logging.Logger;
-import org.jclouds.util.Throwables2;
 
 import com.google.common.base.Predicate;
-import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableMultimap.Builder;
 import com.google.common.collect.Multimap;
@@ -117,13 +114,10 @@
       public Date parseExpires(String expires) {
          try {
             return (expires != null) ? getExpiresDateCodec().toDate(expires) : null;
-         } catch (Exception e) {
-            if (Throwables2.getFirstThrowableOfType(e, ParseException.class) != null) {
-               logger.debug("Invalid Expires header (%s); should be in RFC-1123 format; treating as already expired: %s", expires, e.getMessage());
-               return new Date(0);
-            } else {
-               throw Throwables.propagate(e);
-            }
+         } catch (IllegalArgumentException e) {
+            logger.debug("Invalid Expires header (%s); should be in RFC-1123 format; treating as already expired: %s",
+                  expires, e.getMessage());
+            return new Date(0);
          }
       }
    }
diff --git a/core/src/test/java/org/jclouds/date/DateServiceTest.java b/core/src/test/java/org/jclouds/date/DateServiceTest.java
index 2458a23..7dcf5d2 100644
--- a/core/src/test/java/org/jclouds/date/DateServiceTest.java
+++ b/core/src/test/java/org/jclouds/date/DateServiceTest.java
@@ -23,7 +23,6 @@
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 import org.jclouds.PerformanceTest;
 import org.testng.annotations.BeforeTest;
@@ -96,90 +95,110 @@
    }
 
    @Test
-   public void testIso8601DateParse() throws ExecutionException, InterruptedException {
+   public void testIso8601DateParse() {
       Date dsDate = dateService.iso8601DateParse(testData[0].iso8601DateString);
       assertEquals(dsDate, testData[0].date);
    }
 
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testIso8601DateParseIllegal() {
+      dateService.iso8601DateParse("-1");
+   }
+   
    @Test
-   public void testIso8601DateParseTz() throws ExecutionException, InterruptedException {
+   public void testIso8601DateParseTz() {
       Date dsDate = dateService.iso8601SecondsDateParse(testData[0].iso8601DateStringTz);
       assertEquals(dsDate, testData[0].date);
    }
 
    @Test
-   public void testIso8601SecondsDateParse() throws ExecutionException, InterruptedException {
+   public void testIso8601SecondsDateParse() {
       Date dsDate = dateService.iso8601SecondsDateParse(testData[0].iso8601SecondsDateString);
       assertEquals(dsDate, testData[0].date);
    }
+   
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testIso8601SecondsDateParseIllegal() {
+      dateService.iso8601SecondsDateParse("-1");
+   }
 
    @Test
-   public void testCDateParse() throws ExecutionException, InterruptedException {
+   public void testCDateParse() {
       Date dsDate = dateService.cDateParse(testData[0].cDateString);
       assertEquals(dsDate, testData[0].date);
    }
 
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testCDateParseIllegal() {
+      dateService.cDateParse("foo");
+   }
+   
    @Test
-   public void testRfc822DateParse() throws ExecutionException, InterruptedException {
+   public void testRfc822DateParse() {
       Date dsDate = dateService.rfc822DateParse(testData[0].rfc822DateString);
       assertEquals(dsDate, testData[0].date);
    }
+   
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testRfc822DateParseIllegal() {
+      dateService.rfc822DateParse("foo");
+   }
 
    @Test
-   public void testIso8601DateFormat() throws ExecutionException, InterruptedException {
+   public void testIso8601DateFormat() {
       String dsString = dateService.iso8601DateFormat(testData[0].date);
       assertEquals(dsString, testData[0].iso8601DateString);
    }
 
    @Test
-   public void testIso8601SecondsDateFormat() throws ExecutionException, InterruptedException {
+   public void testIso8601SecondsDateFormat() {
       String dsString = dateService.iso8601SecondsDateFormat(testData[0].date);
       assertEquals(dsString, testData[0].iso8601SecondsDateString);
    }
 
    @Test
-   public void testCDateFormat() throws ExecutionException, InterruptedException {
+   public void testCDateFormat() {
       String dsString = dateService.cDateFormat(testData[0].date);
       assertEquals(dsString, testData[0].cDateString);
    }
 
    @Test
-   public void testRfc822DateFormat() throws ExecutionException, InterruptedException {
+   public void testRfc822DateFormat() {
       String dsString = dateService.rfc822DateFormat(testData[0].date);
       assertEquals(dsString, testData[0].rfc822DateString);
    }
 
    @Test
-   void testIso8601DateFormatResponseTime() throws ExecutionException, InterruptedException {
+   void testIso8601DateFormatResponseTime() {
       for (int i = 0; i < LOOP_COUNT; i++)
          dateService.iso8601DateFormat();
    }
 
    @Test
-   void testFromSeconds() throws ExecutionException, InterruptedException {
+   void testFromSeconds() {
       long seconds = 1254008225;
       Date date = dateService.fromSeconds(seconds);
       assertEquals(dateService.iso8601SecondsDateFormat(date), "2009-09-26T23:37:05Z");
    }
 
    @Test
-   void testTz() throws ExecutionException, InterruptedException {
+   void testTz() {
       assertEquals(dateService.iso8601SecondsDateParse("2011-05-26T02:14:13-04:00").getTime(), 1306390453000l);
    }
    
    @Test
-   void testTzNoT() throws ExecutionException, InterruptedException {
+   void testTzNoT() {
       assertEquals(dateService.iso8601DateParse("2011-05-25 16:12:21.656+0000").getTime(), 1306339941656l);
    }
 
    @Test
-   void testRfc822DateFormatResponseTime() throws ExecutionException, InterruptedException {
+   void testRfc822DateFormatResponseTime() {
       for (int i = 0; i < LOOP_COUNT; i++)
          dateService.rfc822DateFormat();
    }
 
    @Test
-   void testCDateFormatResponseTime() throws ExecutionException, InterruptedException {
+   void testCDateFormatResponseTime() {
       for (int i = 0; i < LOOP_COUNT; i++)
          dateService.cDateFormat();
    }
@@ -212,7 +231,7 @@
    }
 
    @Test
-   void testParseIso8601DateSerialResponseTime() throws ExecutionException, InterruptedException {
+   void testParseIso8601DateSerialResponseTime() {
       for (int i = 0; i < LOOP_COUNT; i++)
          dateService.iso8601DateParse(testData[0].iso8601DateString);
    }
diff --git a/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java b/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java
new file mode 100644
index 0000000..6c6f12a
--- /dev/null
+++ b/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.date.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.util.Date;
+
+import org.jclouds.date.DateCodec;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * 
+ * @author aled
+ * 
+ */
+@Test(testName = "DateServiceDateCodecFactoryTest")
+public class DateServiceDateCodecFactoryTest {
+
+   private DateServiceDateCodecFactory simpleDateCodecFactory;
+   private DateCodec rfc1123Codec;
+
+   @BeforeMethod
+   public void setUp() {
+      simpleDateCodecFactory = new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(
+            new SimpleDateFormatDateService()));
+      rfc1123Codec = simpleDateCodecFactory.rfc1123();
+   }
+
+   @Test
+   public void testCodecForRfc1123() {
+      Date date = new Date(1000);
+      assertEquals(rfc1123Codec.toDate(rfc1123Codec.toString(date)), date);
+
+      assertEquals(rfc1123Codec.toDate("Thu, 01 Dec 1994 16:00:00 GMT"), new Date(786297600000L));
+   }
+
+   @Test
+   public void testCodecForRfc1123ThrowsParseExceptionWhenMalformed() {
+      try {
+         rfc1123Codec.toDate("wrong");
+         fail();
+      } catch (IllegalArgumentException e) {
+      }
+   }
+}
diff --git a/core/src/test/java/org/jclouds/date/internal/SimpleDateCodecFactoryTest.java b/core/src/test/java/org/jclouds/date/internal/SimpleDateCodecFactoryTest.java
deleted file mode 100644
index 7e03049..0000000
--- a/core/src/test/java/org/jclouds/date/internal/SimpleDateCodecFactoryTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.jclouds.date.internal;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.fail;
-
-import java.text.ParseException;
-import java.util.Date;
-
-import org.jclouds.date.DateCodec;
-import org.jclouds.util.Throwables2;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-public class SimpleDateCodecFactoryTest {
-
-   private SimpleDateCodecFactory simpleDateCodecFactory;
-   private DateCodec rfc1123Codec;
-
-   @BeforeMethod
-   public void setUp() throws Exception {
-      simpleDateCodecFactory = new SimpleDateCodecFactory(new SimpleDateFormatDateService());
-      rfc1123Codec = simpleDateCodecFactory.rfc1123();
-   }
-   
-   @Test
-   public void testCodecForRfc1123() throws Exception {
-      Date date = new Date(1000);
-      assertEquals(rfc1123Codec.toDate(rfc1123Codec.toString(date)), date);
-      
-      assertEquals(rfc1123Codec.toDate("Thu, 01 Dec 1994 16:00:00 GMT"), new Date(786297600000L));
-   }
-   
-   @Test
-   public void testCodecForRfc1123ThrowsParseExceptionWhenMalformed() throws Exception {
-      try {
-         rfc1123Codec.toDate("wrong");
-         fail();
-      } catch (Exception e) {
-         if (Throwables2.getFirstThrowableOfType(e, ParseException.class) == null) {
-            throw e;
-         }
-      }
-   }
-}
diff --git a/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java b/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java
index 1672e5a..6861f9d 100644
--- a/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java
+++ b/core/src/test/java/org/jclouds/http/handlers/BackoffLimitedRetryHandlerTest.java
@@ -34,7 +34,8 @@
 import javax.net.ssl.SSLSession;
 import javax.ws.rs.core.UriBuilder;
 
-import org.jclouds.date.internal.SimpleDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
 import org.jclouds.date.internal.SimpleDateFormatDateService;
 import org.jclouds.http.BaseJettyTest;
 import org.jclouds.http.HttpCommand;
@@ -47,8 +48,8 @@
 import org.jclouds.http.internal.HttpWire;
 import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
 import org.jclouds.io.ContentMetadataCodec;
-import org.jclouds.io.Payloads;
 import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
+import org.jclouds.io.Payloads;
 import org.jclouds.rest.internal.RestAnnotationProcessor;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
@@ -116,7 +117,7 @@
       BackoffLimitedRetryHandler backoff = new BackoffLimitedRetryHandler();
       HttpUtils utils = new HttpUtils(0, 500, 1, 1);
       ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(
-               new SimpleDateCodecFactory(new SimpleDateFormatDateService()));
+               new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(new SimpleDateFormatDateService())));
       RedirectionRetryHandler retry = new RedirectionRetryHandler(uriBuilderProvider, backoff);
       JavaUrlHttpCommandExecutorService httpService = new JavaUrlHttpCommandExecutorService(utils, 
                contentMetadataCodec, execService,
diff --git a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java
index 7987f33..38630f5 100644
--- a/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java
+++ b/core/src/test/java/org/jclouds/rest/internal/BaseRestClientExpectTest.java
@@ -49,7 +49,8 @@
 import org.jclouds.concurrent.MoreExecutors;
 import org.jclouds.concurrent.SingleThreaded;
 import org.jclouds.concurrent.config.ConfiguresExecutorService;
-import org.jclouds.date.internal.SimpleDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
 import org.jclouds.date.internal.SimpleDateFormatDateService;
 import org.jclouds.http.HttpCommandExecutorService;
 import org.jclouds.http.HttpRequest;
@@ -123,7 +124,7 @@
    protected String provider = "mock";
 
    protected ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(
-            new SimpleDateCodecFactory(new SimpleDateFormatDateService()));
+            new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(new SimpleDateFormatDateService())));
    
    /**
     * Override this to supply alternative bindings for use in the test. This is commonly used to
diff --git a/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java b/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
index 768ec59..3fd8549 100644
--- a/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
+++ b/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
@@ -48,6 +48,7 @@
 import org.jclouds.demo.paas.service.taskqueue.TaskQueue;
 import org.jclouds.demo.tweetstore.config.util.CredentialsCollector;
 import org.jclouds.demo.tweetstore.controller.AddTweetsController;
+import org.jclouds.demo.tweetstore.controller.ClearTweetsController;
 import org.jclouds.demo.tweetstore.controller.EnqueueStoresController;
 import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
 import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
@@ -173,6 +174,11 @@
        return new EnqueueStoresController(providerTypeToBlobStoreMap, queue, baseUrl);
    }
 
+   @Bean
+   public ClearTweetsController clearTweetsController() {
+      return new ClearTweetsController(providerTypeToBlobStoreMap, container);
+   }
+
    private void injectServletConfig(Servlet servlet) {
       LOGGER.trace("About to inject servlet config '%s'", servletConfig);
       try {
@@ -195,6 +201,7 @@
       urlMap.put("/store/*", storeTweetsController());
       urlMap.put("/tweets/*", addTweetsController());
       urlMap.put("/stores/*", enqueueStoresController());
+      urlMap.put("/clear/*", clearTweetsController());
       mapping.setUrlMap(urlMap);
       /*
        * "/store", "/tweets" and "/stores" are part of the servlet mapping and thus 
diff --git a/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java b/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
new file mode 100644
index 0000000..e08cfbc
--- /dev/null
+++ b/demos/tweetstore/cf-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Grab tweets related to me and store them into blobstores
+ * 
+ * @author Adrian Cole
+ */
+@Singleton
+public class ClearTweetsController extends HttpServlet {
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7215420527854203714L;
+
+   private final Map<String, BlobStoreContext> contexts;
+   private final String container;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   @VisibleForTesting
+   public ClearTweetsController(Map<String, BlobStoreContext> contexts,
+         @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
+      this.container = container;
+      this.contexts = contexts;
+   }
+
+   @VisibleForTesting
+   public void clearContainer(String contextName) {
+        BlobStoreContext context = checkNotNull(contexts.get(contextName),
+                "no context for %s in %s", contextName, contexts.keySet());
+        try {
+            context.getBlobStore().clearContainer(container);
+        } catch (Exception e) {
+            logger.error(e, "Error clearing tweets in %s/%s", container, context);
+        }
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+       if (nullToEmpty(request.getHeader("X-Originator")).equals("admin")) {
+         try {
+            String contextName = checkNotNull(request.getHeader("context"), "missing header context");
+            logger.info("clearing tweets in %s/%s", container, contextName);
+            clearContainer(contextName);
+            logger.debug("done clearing tweets");
+            response.setContentType(MediaType.TEXT_PLAIN);
+            response.getWriter().println("Done!");
+         } catch (Exception e) {
+            logger.error(e, "Error clearing tweets");
+            throw new ServletException(e);
+         }
+      } else {
+         response.sendError(401);
+      }
+   }
+}
\ No newline at end of file
diff --git a/demos/tweetstore/cf-tweetstore-spring/src/main/webapp/WEB-INF/web.xml b/demos/tweetstore/cf-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
index 4b760f2..51dd074 100644
--- a/demos/tweetstore/cf-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
+++ b/demos/tweetstore/cf-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
@@ -67,6 +67,10 @@
         <servlet-name>dispatcher</servlet-name>
         <url-pattern>/stores/*</url-pattern>
     </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>dispatcher</servlet-name>
+        <url-pattern>/clear/*</url-pattern>
+    </servlet-mapping>
 
     <welcome-file-list>
         <welcome-file>index.jsp</welcome-file>
diff --git a/demos/tweetstore/cf-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java b/demos/tweetstore/cf-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
new file mode 100644
index 0000000..81f0b9f
--- /dev/null
+++ b/demos/tweetstore/cf-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code AddTweetsController}
+ * 
+ * @author Adrian Cole
+ */
+@Test(groups = "unit")
+public class ClearTweetsControllerTest {
+
+    Map<String, BlobStoreContext> createBlobStores(String container) throws InterruptedException, ExecutionException {
+        TransientApiMetadata transientApiMetadata = TransientApiMetadata.builder().build();
+        Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext>of(
+                        "test1", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class),
+                        "test2", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class));
+        for (BlobStoreContext blobstore : contexts.values()) {
+            blobstore.getBlobStore().createContainerInLocation(null, container);
+            Blob blob = blobstore.getAsyncBlobStore().blobBuilder("1").build();
+            blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
+            blob.setPayload("I love beans!");
+            blobstore.getBlobStore().putBlob(container, blob);
+        }
+        return contexts;
+    }
+
+    public void testClearTweets() throws IOException, InterruptedException, ExecutionException {
+        String container = ClearTweetsControllerTest.class.getName() + "#container";
+        Map<String, BlobStoreContext> contexts = createBlobStores(container);
+
+        ClearTweetsController controller = new ClearTweetsController(contexts,
+                container);
+        controller.clearContainer("test1");
+        controller.clearContainer("test2");
+
+        for (BlobStoreContext context : contexts.values()) {
+            assertEquals(context.getBlobStore().countBlobs(container), 0, context.toString());
+        }
+    }
+}
diff --git a/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java b/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
index c925dc5..9bfba4a 100644
--- a/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
+++ b/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/config/SpringServletConfig.java
@@ -46,6 +46,7 @@
 import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.demo.tweetstore.config.util.CredentialsCollector;
 import org.jclouds.demo.tweetstore.controller.AddTweetsController;
+import org.jclouds.demo.tweetstore.controller.ClearTweetsController;
 import org.jclouds.demo.tweetstore.controller.EnqueueStoresController;
 import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
 import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
@@ -172,6 +173,11 @@
       return new EnqueueStoresController(providerTypeToBlobStoreMap, queue);
    }
 
+   @Bean
+   public ClearTweetsController clearTweetsController() {
+      return new ClearTweetsController(providerTypeToBlobStoreMap, container);
+   }
+
    private void injectServletConfig(Servlet servlet) {
       LOGGER.trace("About to inject servlet config '%s'", servletConfig);
       try {
@@ -194,6 +200,7 @@
       urlMap.put("/store/*", storeTweetsController());
       urlMap.put("/tweets/*", addTweetsController());
       urlMap.put("/stores/*", enqueueStoresController());
+      urlMap.put("/clear/*", clearTweetsController());
       mapping.setUrlMap(urlMap);
       /*
        * "/store", "/tweets" and "/stores" are part of the servlet mapping and thus 
diff --git a/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java b/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
new file mode 100644
index 0000000..e08cfbc
--- /dev/null
+++ b/demos/tweetstore/gae-tweetstore-spring/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Grab tweets related to me and store them into blobstores
+ * 
+ * @author Adrian Cole
+ */
+@Singleton
+public class ClearTweetsController extends HttpServlet {
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7215420527854203714L;
+
+   private final Map<String, BlobStoreContext> contexts;
+   private final String container;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   @VisibleForTesting
+   public ClearTweetsController(Map<String, BlobStoreContext> contexts,
+         @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
+      this.container = container;
+      this.contexts = contexts;
+   }
+
+   @VisibleForTesting
+   public void clearContainer(String contextName) {
+        BlobStoreContext context = checkNotNull(contexts.get(contextName),
+                "no context for %s in %s", contextName, contexts.keySet());
+        try {
+            context.getBlobStore().clearContainer(container);
+        } catch (Exception e) {
+            logger.error(e, "Error clearing tweets in %s/%s", container, context);
+        }
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+       if (nullToEmpty(request.getHeader("X-Originator")).equals("admin")) {
+         try {
+            String contextName = checkNotNull(request.getHeader("context"), "missing header context");
+            logger.info("clearing tweets in %s/%s", container, contextName);
+            clearContainer(contextName);
+            logger.debug("done clearing tweets");
+            response.setContentType(MediaType.TEXT_PLAIN);
+            response.getWriter().println("Done!");
+         } catch (Exception e) {
+            logger.error(e, "Error clearing tweets");
+            throw new ServletException(e);
+         }
+      } else {
+         response.sendError(401);
+      }
+   }
+}
\ No newline at end of file
diff --git a/demos/tweetstore/gae-tweetstore-spring/src/main/webapp/WEB-INF/web.xml b/demos/tweetstore/gae-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
index 971ada2..3853142 100644
--- a/demos/tweetstore/gae-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
+++ b/demos/tweetstore/gae-tweetstore-spring/src/main/webapp/WEB-INF/web.xml
@@ -45,6 +45,10 @@
         <servlet-name>dispatcher</servlet-name>
         <url-pattern>/stores/*</url-pattern>
     </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>dispatcher</servlet-name>
+        <url-pattern>/clear/*</url-pattern>
+    </servlet-mapping>
 
     <!-- limit submission of storage tasks to the cron job -->
     <security-constraint>
diff --git a/demos/tweetstore/gae-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java b/demos/tweetstore/gae-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
new file mode 100644
index 0000000..81f0b9f
--- /dev/null
+++ b/demos/tweetstore/gae-tweetstore-spring/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code AddTweetsController}
+ * 
+ * @author Adrian Cole
+ */
+@Test(groups = "unit")
+public class ClearTweetsControllerTest {
+
+    Map<String, BlobStoreContext> createBlobStores(String container) throws InterruptedException, ExecutionException {
+        TransientApiMetadata transientApiMetadata = TransientApiMetadata.builder().build();
+        Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext>of(
+                        "test1", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class),
+                        "test2", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class));
+        for (BlobStoreContext blobstore : contexts.values()) {
+            blobstore.getBlobStore().createContainerInLocation(null, container);
+            Blob blob = blobstore.getAsyncBlobStore().blobBuilder("1").build();
+            blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
+            blob.setPayload("I love beans!");
+            blobstore.getBlobStore().putBlob(container, blob);
+        }
+        return contexts;
+    }
+
+    public void testClearTweets() throws IOException, InterruptedException, ExecutionException {
+        String container = ClearTweetsControllerTest.class.getName() + "#container";
+        Map<String, BlobStoreContext> contexts = createBlobStores(container);
+
+        ClearTweetsController controller = new ClearTweetsController(contexts,
+                container);
+        controller.clearContainer("test1");
+        controller.clearContainer("test2");
+
+        for (BlobStoreContext context : contexts.values()) {
+            assertEquals(context.getBlobStore().countBlobs(container), 0, context.toString());
+        }
+    }
+}
diff --git a/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java b/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
index 2db9a45..4be8fb5 100644
--- a/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
+++ b/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
@@ -42,6 +42,7 @@
 import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.demo.tweetstore.config.util.CredentialsCollector;
 import org.jclouds.demo.tweetstore.controller.AddTweetsController;
+import org.jclouds.demo.tweetstore.controller.ClearTweetsController;
 import org.jclouds.demo.tweetstore.controller.EnqueueStoresController;
 import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
 import org.jclouds.gae.config.GoogleAppEngineConfigurationModule;
@@ -148,6 +149,7 @@
             serve("/store/*").with(StoreTweetsController.class);
             serve("/tweets/*").with(AddTweetsController.class);
             serve("/stores/*").with(EnqueueStoresController.class);
+            serve("/clear/*").with(ClearTweetsController.class);
          }
       });
    }
diff --git a/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java b/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
new file mode 100644
index 0000000..e08cfbc
--- /dev/null
+++ b/demos/tweetstore/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Grab tweets related to me and store them into blobstores
+ * 
+ * @author Adrian Cole
+ */
+@Singleton
+public class ClearTweetsController extends HttpServlet {
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7215420527854203714L;
+
+   private final Map<String, BlobStoreContext> contexts;
+   private final String container;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   @VisibleForTesting
+   public ClearTweetsController(Map<String, BlobStoreContext> contexts,
+         @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
+      this.container = container;
+      this.contexts = contexts;
+   }
+
+   @VisibleForTesting
+   public void clearContainer(String contextName) {
+        BlobStoreContext context = checkNotNull(contexts.get(contextName),
+                "no context for %s in %s", contextName, contexts.keySet());
+        try {
+            context.getBlobStore().clearContainer(container);
+        } catch (Exception e) {
+            logger.error(e, "Error clearing tweets in %s/%s", container, context);
+        }
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+       if (nullToEmpty(request.getHeader("X-Originator")).equals("admin")) {
+         try {
+            String contextName = checkNotNull(request.getHeader("context"), "missing header context");
+            logger.info("clearing tweets in %s/%s", container, contextName);
+            clearContainer(contextName);
+            logger.debug("done clearing tweets");
+            response.setContentType(MediaType.TEXT_PLAIN);
+            response.getWriter().println("Done!");
+         } catch (Exception e) {
+            logger.error(e, "Error clearing tweets");
+            throw new ServletException(e);
+         }
+      } else {
+         response.sendError(401);
+      }
+   }
+}
\ No newline at end of file
diff --git a/demos/tweetstore/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java b/demos/tweetstore/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
new file mode 100644
index 0000000..81f0b9f
--- /dev/null
+++ b/demos/tweetstore/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code AddTweetsController}
+ * 
+ * @author Adrian Cole
+ */
+@Test(groups = "unit")
+public class ClearTweetsControllerTest {
+
+    Map<String, BlobStoreContext> createBlobStores(String container) throws InterruptedException, ExecutionException {
+        TransientApiMetadata transientApiMetadata = TransientApiMetadata.builder().build();
+        Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext>of(
+                        "test1", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class),
+                        "test2", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class));
+        for (BlobStoreContext blobstore : contexts.values()) {
+            blobstore.getBlobStore().createContainerInLocation(null, container);
+            Blob blob = blobstore.getAsyncBlobStore().blobBuilder("1").build();
+            blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
+            blob.setPayload("I love beans!");
+            blobstore.getBlobStore().putBlob(container, blob);
+        }
+        return contexts;
+    }
+
+    public void testClearTweets() throws IOException, InterruptedException, ExecutionException {
+        String container = ClearTweetsControllerTest.class.getName() + "#container";
+        Map<String, BlobStoreContext> contexts = createBlobStores(container);
+
+        ClearTweetsController controller = new ClearTweetsController(contexts,
+                container);
+        controller.clearContainer("test1");
+        controller.clearContainer("test2");
+
+        for (BlobStoreContext context : contexts.values()) {
+            assertEquals(context.getBlobStore().countBlobs(container), 0, context.toString());
+        }
+    }
+}
diff --git a/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java b/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
index 24d0c94..c034343 100644
--- a/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
+++ b/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
@@ -45,6 +45,7 @@
 import org.jclouds.demo.tweetstore.config.util.CredentialsCollector;
 import org.jclouds.demo.tweetstore.config.util.PropertiesLoader;
 import org.jclouds.demo.tweetstore.controller.AddTweetsController;
+import org.jclouds.demo.tweetstore.controller.ClearTweetsController;
 import org.jclouds.demo.tweetstore.controller.EnqueueStoresController;
 import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
 
@@ -138,6 +139,7 @@
                 serve("/store/*").with(StoreTweetsController.class);
                 serve("/tweets/*").with(AddTweetsController.class);
                 serve("/stores/*").with(EnqueueStoresController.class);
+                serve("/clear/*").with(ClearTweetsController.class);
             }
         });
     }
diff --git a/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java b/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
new file mode 100644
index 0000000..e08cfbc
--- /dev/null
+++ b/demos/tweetstore/rhcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Grab tweets related to me and store them into blobstores
+ * 
+ * @author Adrian Cole
+ */
+@Singleton
+public class ClearTweetsController extends HttpServlet {
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7215420527854203714L;
+
+   private final Map<String, BlobStoreContext> contexts;
+   private final String container;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   @VisibleForTesting
+   public ClearTweetsController(Map<String, BlobStoreContext> contexts,
+         @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
+      this.container = container;
+      this.contexts = contexts;
+   }
+
+   @VisibleForTesting
+   public void clearContainer(String contextName) {
+        BlobStoreContext context = checkNotNull(contexts.get(contextName),
+                "no context for %s in %s", contextName, contexts.keySet());
+        try {
+            context.getBlobStore().clearContainer(container);
+        } catch (Exception e) {
+            logger.error(e, "Error clearing tweets in %s/%s", container, context);
+        }
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+       if (nullToEmpty(request.getHeader("X-Originator")).equals("admin")) {
+         try {
+            String contextName = checkNotNull(request.getHeader("context"), "missing header context");
+            logger.info("clearing tweets in %s/%s", container, contextName);
+            clearContainer(contextName);
+            logger.debug("done clearing tweets");
+            response.setContentType(MediaType.TEXT_PLAIN);
+            response.getWriter().println("Done!");
+         } catch (Exception e) {
+            logger.error(e, "Error clearing tweets");
+            throw new ServletException(e);
+         }
+      } else {
+         response.sendError(401);
+      }
+   }
+}
\ No newline at end of file
diff --git a/demos/tweetstore/rhcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java b/demos/tweetstore/rhcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
new file mode 100644
index 0000000..81f0b9f
--- /dev/null
+++ b/demos/tweetstore/rhcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code AddTweetsController}
+ * 
+ * @author Adrian Cole
+ */
+@Test(groups = "unit")
+public class ClearTweetsControllerTest {
+
+    Map<String, BlobStoreContext> createBlobStores(String container) throws InterruptedException, ExecutionException {
+        TransientApiMetadata transientApiMetadata = TransientApiMetadata.builder().build();
+        Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext>of(
+                        "test1", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class),
+                        "test2", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class));
+        for (BlobStoreContext blobstore : contexts.values()) {
+            blobstore.getBlobStore().createContainerInLocation(null, container);
+            Blob blob = blobstore.getAsyncBlobStore().blobBuilder("1").build();
+            blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
+            blob.setPayload("I love beans!");
+            blobstore.getBlobStore().putBlob(container, blob);
+        }
+        return contexts;
+    }
+
+    public void testClearTweets() throws IOException, InterruptedException, ExecutionException {
+        String container = ClearTweetsControllerTest.class.getName() + "#container";
+        Map<String, BlobStoreContext> contexts = createBlobStores(container);
+
+        ClearTweetsController controller = new ClearTweetsController(contexts,
+                container);
+        controller.clearContainer("test1");
+        controller.clearContainer("test2");
+
+        for (BlobStoreContext context : contexts.values()) {
+            assertEquals(context.getBlobStore().countBlobs(container), 0, context.toString());
+        }
+    }
+}
diff --git a/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java b/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
index 24d0c94..c034343 100644
--- a/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
+++ b/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java
@@ -45,6 +45,7 @@
 import org.jclouds.demo.tweetstore.config.util.CredentialsCollector;
 import org.jclouds.demo.tweetstore.config.util.PropertiesLoader;
 import org.jclouds.demo.tweetstore.controller.AddTweetsController;
+import org.jclouds.demo.tweetstore.controller.ClearTweetsController;
 import org.jclouds.demo.tweetstore.controller.EnqueueStoresController;
 import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
 
@@ -138,6 +139,7 @@
                 serve("/store/*").with(StoreTweetsController.class);
                 serve("/tweets/*").with(AddTweetsController.class);
                 serve("/stores/*").with(EnqueueStoresController.class);
+                serve("/clear/*").with(ClearTweetsController.class);
             }
         });
     }
diff --git a/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java b/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
new file mode 100644
index 0000000..e08cfbc
--- /dev/null
+++ b/demos/tweetstore/runatcloud-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/ClearTweetsController.java
@@ -0,0 +1,96 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.nullToEmpty;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Grab tweets related to me and store them into blobstores
+ * 
+ * @author Adrian Cole
+ */
+@Singleton
+public class ClearTweetsController extends HttpServlet {
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7215420527854203714L;
+
+   private final Map<String, BlobStoreContext> contexts;
+   private final String container;
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+
+   @Inject
+   @VisibleForTesting
+   public ClearTweetsController(Map<String, BlobStoreContext> contexts,
+         @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
+      this.container = container;
+      this.contexts = contexts;
+   }
+
+   @VisibleForTesting
+   public void clearContainer(String contextName) {
+        BlobStoreContext context = checkNotNull(contexts.get(contextName),
+                "no context for %s in %s", contextName, contexts.keySet());
+        try {
+            context.getBlobStore().clearContainer(container);
+        } catch (Exception e) {
+            logger.error(e, "Error clearing tweets in %s/%s", container, context);
+        }
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+       if (nullToEmpty(request.getHeader("X-Originator")).equals("admin")) {
+         try {
+            String contextName = checkNotNull(request.getHeader("context"), "missing header context");
+            logger.info("clearing tweets in %s/%s", container, contextName);
+            clearContainer(contextName);
+            logger.debug("done clearing tweets");
+            response.setContentType(MediaType.TEXT_PLAIN);
+            response.getWriter().println("Done!");
+         } catch (Exception e) {
+            logger.error(e, "Error clearing tweets");
+            throw new ServletException(e);
+         }
+      } else {
+         response.sendError(401);
+      }
+   }
+}
\ No newline at end of file
diff --git a/demos/tweetstore/runatcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java b/demos/tweetstore/runatcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
new file mode 100644
index 0000000..81f0b9f
--- /dev/null
+++ b/demos/tweetstore/runatcloud-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/ClearTweetsControllerTest.java
@@ -0,0 +1,72 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.demo.tweetstore.controller;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.TransientApiMetadata;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests behavior of {@code AddTweetsController}
+ * 
+ * @author Adrian Cole
+ */
+@Test(groups = "unit")
+public class ClearTweetsControllerTest {
+
+    Map<String, BlobStoreContext> createBlobStores(String container) throws InterruptedException, ExecutionException {
+        TransientApiMetadata transientApiMetadata = TransientApiMetadata.builder().build();
+        Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext>of(
+                        "test1", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class),
+                        "test2", ContextBuilder.newBuilder(transientApiMetadata).build(BlobStoreContext.class));
+        for (BlobStoreContext blobstore : contexts.values()) {
+            blobstore.getBlobStore().createContainerInLocation(null, container);
+            Blob blob = blobstore.getAsyncBlobStore().blobBuilder("1").build();
+            blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
+            blob.setPayload("I love beans!");
+            blobstore.getBlobStore().putBlob(container, blob);
+        }
+        return contexts;
+    }
+
+    public void testClearTweets() throws IOException, InterruptedException, ExecutionException {
+        String container = ClearTweetsControllerTest.class.getName() + "#container";
+        Map<String, BlobStoreContext> contexts = createBlobStores(container);
+
+        ClearTweetsController controller = new ClearTweetsController(contexts,
+                container);
+        controller.clearContainer("test1");
+        controller.clearContainer("test2");
+
+        for (BlobStoreContext context : contexts.values()) {
+            assertEquals(context.getBlobStore().countBlobs(container), 0, context.toString());
+        }
+    }
+}
diff --git a/drivers/gae/src/test/java/org/jclouds/gae/ConvertToGaeRequestTest.java b/drivers/gae/src/test/java/org/jclouds/gae/ConvertToGaeRequestTest.java
index ea10bb2..83c6c7e 100644
--- a/drivers/gae/src/test/java/org/jclouds/gae/ConvertToGaeRequestTest.java
+++ b/drivers/gae/src/test/java/org/jclouds/gae/ConvertToGaeRequestTest.java
@@ -22,7 +22,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URI;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
@@ -32,7 +31,8 @@
 import javax.ws.rs.core.HttpHeaders;
 
 import org.jclouds.crypto.Crypto;
-import org.jclouds.date.internal.SimpleDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
 import org.jclouds.date.internal.SimpleDateFormatDateService;
 import org.jclouds.encryption.internal.JCECrypto;
 import org.jclouds.http.HttpRequest;
@@ -72,10 +72,10 @@
    }
 
    @BeforeTest
-   void setupClient() throws MalformedURLException {
+   void setupClient() {
       endPoint = URI.create("http://localhost:80/foo");
       req = new ConvertToGaeRequest(new HttpUtils(0, 0, 0, 0), new DefaultContentMetadataCodec(
-               new SimpleDateCodecFactory(new SimpleDateFormatDateService())));
+               new DateServiceDateCodecFactory(new DateServiceRfc1123Codec(new SimpleDateFormatDateService()))));
 
    }
 
diff --git a/drivers/gae/src/test/java/org/jclouds/gae/ConvertToJcloudsResponseTest.java b/drivers/gae/src/test/java/org/jclouds/gae/ConvertToJcloudsResponseTest.java
index bce7f1d..51ec993 100644
--- a/drivers/gae/src/test/java/org/jclouds/gae/ConvertToJcloudsResponseTest.java
+++ b/drivers/gae/src/test/java/org/jclouds/gae/ConvertToJcloudsResponseTest.java
@@ -24,7 +24,6 @@
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
-import java.net.MalformedURLException;
 import java.net.URI;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
@@ -34,7 +33,8 @@
 import javax.ws.rs.core.HttpHeaders;
 
 import org.jclouds.crypto.Crypto;
-import org.jclouds.date.internal.SimpleDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory;
+import org.jclouds.date.internal.DateServiceDateCodecFactory.DateServiceRfc1123Codec;
 import org.jclouds.date.internal.SimpleDateFormatDateService;
 import org.jclouds.encryption.internal.JCECrypto;
 import org.jclouds.http.HttpResponse;
@@ -68,10 +68,10 @@
    }
 
    @BeforeTest
-   void setupClient() throws MalformedURLException {
+   void setupClient() {
       endPoint = URI.create("http://localhost:80/foo");
-      req = new ConvertToJcloudsResponse(new DefaultContentMetadataCodec(
-                new SimpleDateCodecFactory(new SimpleDateFormatDateService())));
+      req = new ConvertToJcloudsResponse(new DefaultContentMetadataCodec(new DateServiceDateCodecFactory(
+            new DateServiceRfc1123Codec(new SimpleDateFormatDateService()))));
    }
 
    @Test
diff --git a/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java b/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java
index 4382b4b..e026125 100644
--- a/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java
+++ b/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java
@@ -111,6 +111,8 @@
    }
 
    public final Date iso8601DateParse(String toParse) {
+      if (toParse.length() < 10)
+         throw new IllegalArgumentException("incorrect date format " + toParse);
       String tz = findTZ(toParse);
       toParse = trimToMillis(toParse);
       toParse = trimTZ(toParse);
@@ -121,6 +123,8 @@
    }
 
    public final Date iso8601SecondsDateParse(String toParse) {
+      if (toParse.length() < 10)
+         throw new IllegalArgumentException("incorrect date format " + toParse);
       String tz = findTZ(toParse);
       toParse = trimToMillis(toParse);
       toParse = trimTZ(toParse);
diff --git a/drivers/joda/src/test/java/org/jclouds/date/joda/JodaDateServiceTest.java b/drivers/joda/src/test/java/org/jclouds/date/joda/JodaDateServiceTest.java
index 9efde59..cc3ed5e 100644
--- a/drivers/joda/src/test/java/org/jclouds/date/joda/JodaDateServiceTest.java
+++ b/drivers/joda/src/test/java/org/jclouds/date/joda/JodaDateServiceTest.java
@@ -21,7 +21,6 @@
 import static org.testng.Assert.assertEquals;
 
 import java.util.Date;
-import java.util.concurrent.ExecutionException;
 
 import org.jclouds.date.DateService;
 import org.jclouds.date.DateServiceTest;
@@ -56,14 +55,14 @@
 
    @Override
    @Test
-   public void testRfc822DateFormat() throws ExecutionException, InterruptedException {
+   public void testRfc822DateFormat() {
       String dsString = dateService.rfc822DateFormat(testData[0].date);
       assertEquals(dsString, testData[0].rfc822DateString);
    }
 
    @Override
    @Test(enabled = false)
-   public void testRfc822DateParse() throws ExecutionException, InterruptedException {
+   public void testRfc822DateParse() {
       Date dsDate = dateService.rfc822DateParse(testData[0].rfc822DateString);
       assertEquals(dsDate, testData[0].date);
    }
diff --git a/drivers/sshj/pom.xml b/drivers/sshj/pom.xml
index 2f56867..d8fbcb8 100644
--- a/drivers/sshj/pom.xml
+++ b/drivers/sshj/pom.xml
@@ -89,7 +89,7 @@
         <dependency>
             <groupId>net.schmizz</groupId>
             <artifactId>sshj</artifactId>
-            <version>0.7.0</version>
+            <version>0.8.0</version>
         </dependency>
         <dependency>
             <groupId>commons-io</groupId>
diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3ContainerLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3ContainerLiveTest.java
index 78788e3..f2cf6b9 100644
--- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3ContainerLiveTest.java
+++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/blobstore/integration/AWSS3ContainerLiveTest.java
@@ -23,29 +23,18 @@
 
 import java.io.IOException;
 import java.net.MalformedURLException;
-import java.text.ParseException;
 import java.util.Date;
 import java.util.NoSuchElementException;
 import java.util.Set;
 
 import org.jclouds.blobstore.BlobStore;
-import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.blobstore.domain.BlobMetadata;
-import org.jclouds.date.DateCodec;
-import org.jclouds.date.internal.SimpleDateCodecFactory;
-import org.jclouds.date.internal.SimpleDateFormatDateService;
 import org.jclouds.domain.Location;
-import org.jclouds.io.ContentMetadataCodec;
-import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
 import org.jclouds.s3.blobstore.integration.S3ContainerLiveTest;
 import org.jclouds.util.Strings2;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.inject.AbstractModule;
-import com.google.inject.Module;
 
 /**
  * @author Adrian Cole
@@ -75,50 +64,6 @@
    }
    
    @Test(groups = { "live" })
-   public void testCreateBlobWithMalformedExpiry() throws InterruptedException, MalformedURLException, IOException {
-      // Create a blob that has a malformed Expires value; requires overriding the ContentMetadataCodec in Guice...
-      final ContentMetadataCodec contentMetadataCodec = new DefaultContentMetadataCodec(new SimpleDateCodecFactory(new SimpleDateFormatDateService())) {
-         @Override
-         protected DateCodec getExpiresDateCodec() {
-            return new DateCodec() {
-               @Override public Date toDate(String date) throws ParseException {
-                  return new Date();
-               }
-               @Override public String toString(Date date) {
-                  return "wrong";
-               }
-            };
-         }
-      };
-      
-      Module customModule = new AbstractModule() {
-         @Override
-         protected void configure() {
-            bind(ContentMetadataCodec.class).toInstance(contentMetadataCodec);
-         }
-      };
-      
-      Iterable<Module> modules = Iterables.concat(setupModules(), ImmutableList.of(customModule));
-      BlobStoreContext naughtyBlobStoreContext = createView(setupProperties(), modules);
-      BlobStore naughtyBlobStore = naughtyBlobStoreContext.getBlobStore();
-      
-      final String containerName = getScratchContainerName();
-      
-      try {
-         final String blobName = "hello";
-         
-         naughtyBlobStore.createContainerInLocation(null, containerName, publicRead());
-         naughtyBlobStore.putBlob(containerName, naughtyBlobStore.blobBuilder(blobName)
-                  .payload(TEST_STRING).expires(new Date(System.currentTimeMillis() + 60*1000)).build());
-
-         assertConsistencyAwareBlobExpiryMetadata(containerName, blobName, new Date(0));
-
-      } finally {
-         recycleContainer(containerName);
-      }
-   }
-   
-   @Test(groups = { "live" })
    public void testCreateBlobInLocation() throws InterruptedException, MalformedURLException, IOException {
       String payload = "my data";
       runCreateContainerInLocation(payload);
diff --git a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java
index 14751d1..aa76afb 100644
--- a/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java
+++ b/scriptbuilder/src/main/java/org/jclouds/scriptbuilder/statements/login/UserAdd.java
@@ -18,6 +18,7 @@
  */
 package org.jclouds.scriptbuilder.statements.login;
 
+import static com.google.common.base.Objects.equal;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.List;
@@ -36,6 +37,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -178,10 +180,10 @@
       checkNotNull(family, "family");
       if (family == OsFamily.WINDOWS)
          throw new UnsupportedOperationException("windows not yet implemented");
-      String homeDir = (home != null) ? home : (defaultHome + "{fs}" + login);
+      String homeDir = (home != null) ? home : (defaultHome + '/' + login);
       ImmutableList.Builder<Statement> statements = ImmutableList.builder();
       // useradd cannot create the default homedir
-      statements.add(Statements.exec("{md} " + homeDir));
+      statements.add(Statements.exec("{md} " + homeDir.substring(0, homeDir.lastIndexOf('/'))));
 
       ImmutableMap.Builder<String, String> userAddOptions = ImmutableMap.builder();
       userAddOptions.put("-s", shell);
@@ -222,27 +224,22 @@
    }
 
    @Override
-   public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + ((login == null) ? 0 : login.hashCode());
-      return result;
+   public boolean equals(Object o) {
+      if (this == o)
+         return true;
+      if (o == null || getClass() != o.getClass())
+         return false;
+      UserAdd that = UserAdd.class.cast(o);
+      return equal(this.login, that.login);
    }
 
    @Override
-   public boolean equals(Object obj) {
-      if (this == obj)
-         return true;
-      if (obj == null)
-         return false;
-      if (getClass() != obj.getClass())
-         return false;
-      UserAdd other = (UserAdd) obj;
-      if (login == null) {
-         if (other.login != null)
-            return false;
-      } else if (!login.equals(other.login))
-         return false;
-      return true;
+   public int hashCode() {
+      return Objects.hashCode(login);
+   }
+
+   @Override
+   public String toString() {
+      return Objects.toStringHelper("").add("login", login).toString();
    }
 }
\ No newline at end of file
diff --git a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/UserAddTest.java b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/UserAddTest.java
index a41a60f..fd11d75 100644
--- a/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/UserAddTest.java
+++ b/scriptbuilder/src/test/java/org/jclouds/scriptbuilder/statements/login/UserAddTest.java
@@ -33,51 +33,51 @@
 
    public void testUNIX() {
       assertEquals(UserAdd.builder().login("me").build().render(OsFamily.UNIX),
-               "mkdir -p /home/users/me\nuseradd -s /bin/bash -m  -d /home/users/me me\nchown -R me /home/users/me\n");
+               "mkdir -p /home/users\nuseradd -s /bin/bash -m  -d /home/users/me me\nchown -R me /home/users/me\n");
    }
 
    public void testWithBaseUNIX() {
       assertEquals(UserAdd.builder().login("me").defaultHome("/export/home").build().render(OsFamily.UNIX),
-               "mkdir -p /export/home/me\nuseradd -s /bin/bash -m  -d /export/home/me me\nchown -R me /export/home/me\n");
+               "mkdir -p /export/home\nuseradd -s /bin/bash -m  -d /export/home/me me\nchown -R me /export/home/me\n");
    }
 
    public void testWithGroupUNIX() {
       assertEquals(UserAdd.builder().login("me").group("wheel").build().render(OsFamily.UNIX),
-               "mkdir -p /home/users/me\ngroupadd -f wheel\nuseradd -s /bin/bash -g wheel -m  -d /home/users/me me\nchown -R me /home/users/me\n");
+               "mkdir -p /home/users\ngroupadd -f wheel\nuseradd -s /bin/bash -g wheel -m  -d /home/users/me me\nchown -R me /home/users/me\n");
    }
 
    public void testWithGroupsUNIX() {
       assertEquals(UserAdd.builder().login("me").groups(ImmutableList.of("wheel", "candy")).build().render(
                OsFamily.UNIX),
-               "mkdir -p /home/users/me\ngroupadd -f wheel\ngroupadd -f candy\nuseradd -s /bin/bash -g wheel -G candy -m  -d /home/users/me me\nchown -R me /home/users/me\n");
+               "mkdir -p /home/users\ngroupadd -f wheel\ngroupadd -f candy\nuseradd -s /bin/bash -g wheel -G candy -m  -d /home/users/me me\nchown -R me /home/users/me\n");
    }
 
    public void testWithPasswordUNIX() {
       String userAdd = UserAdd.builder().login("me").password("foo").group("wheel").build().render(OsFamily.UNIX);
-      assert userAdd.startsWith("mkdir -p /home/users/me\ngroupadd -f wheel\nuseradd -s /bin/bash -g wheel -m  -d /home/users/me -p '$6$") : userAdd;
+      assert userAdd.startsWith("mkdir -p /home/users\ngroupadd -f wheel\nuseradd -s /bin/bash -g wheel -m  -d /home/users/me -p '$6$") : userAdd;
       assert userAdd.endsWith("' me\nchown -R me /home/users/me\n") : userAdd;
    }
 
    public void testWithSshAuthorizedKeyUNIX() {
       assertEquals(
                UserAdd.builder().login("me").authorizeRSAPublicKey("rsapublickey").build().render(OsFamily.UNIX),
-               "mkdir -p /home/users/me\nuseradd -s /bin/bash -m  -d /home/users/me me\nmkdir -p /home/users/me/.ssh\ncat >> /home/users/me/.ssh/authorized_keys <<-'END_OF_JCLOUDS_FILE'\n\trsapublickey\nEND_OF_JCLOUDS_FILE\nchmod 600 /home/users/me/.ssh/authorized_keys\nchown -R me /home/users/me\n");
+               "mkdir -p /home/users\nuseradd -s /bin/bash -m  -d /home/users/me me\nmkdir -p /home/users/me/.ssh\ncat >> /home/users/me/.ssh/authorized_keys <<-'END_OF_JCLOUDS_FILE'\n\trsapublickey\nEND_OF_JCLOUDS_FILE\nchmod 600 /home/users/me/.ssh/authorized_keys\nchown -R me /home/users/me\n");
    }
 
    public void testWithSshInstalledKeyUNIX() {
       assertEquals(
                UserAdd.builder().login("me").installRSAPrivateKey("rsaprivate").build().render(OsFamily.UNIX),
-               "mkdir -p /home/users/me\nuseradd -s /bin/bash -m  -d /home/users/me me\nmkdir -p /home/users/me/.ssh\nrm /home/users/me/.ssh/id_rsa\ncat >> /home/users/me/.ssh/id_rsa <<-'END_OF_JCLOUDS_FILE'\n\trsaprivate\nEND_OF_JCLOUDS_FILE\nchmod 600 /home/users/me/.ssh/id_rsa\nchown -R me /home/users/me\n");
+               "mkdir -p /home/users\nuseradd -s /bin/bash -m  -d /home/users/me me\nmkdir -p /home/users/me/.ssh\nrm /home/users/me/.ssh/id_rsa\ncat >> /home/users/me/.ssh/id_rsa <<-'END_OF_JCLOUDS_FILE'\n\trsaprivate\nEND_OF_JCLOUDS_FILE\nchmod 600 /home/users/me/.ssh/id_rsa\nchown -R me /home/users/me\n");
    }
 
    public void testWithHomeUNIX() {
       assertEquals(UserAdd.builder().login("me").home("/myhome/myme").build().render(
                OsFamily.UNIX),
-               "mkdir -p /myhome/myme\nuseradd -s /bin/bash -m  -d /myhome/myme me\nchown -R me /myhome/myme\n");
+               "mkdir -p /myhome\nuseradd -s /bin/bash -m  -d /myhome/myme me\nchown -R me /myhome/myme\n");
       
       assertEquals(UserAdd.builder().login("me").home("/myhome/myme").defaultHome("/ignoreddefault").build().render(
                               OsFamily.UNIX),
-                              "mkdir -p /myhome/myme\nuseradd -s /bin/bash -m  -d /myhome/myme me\nchown -R me /myhome/myme\n");
+                              "mkdir -p /myhome\nuseradd -s /bin/bash -m  -d /myhome/myme me\nchown -R me /myhome/myme\n");
    }
 
    @Test(expectedExceptions = UnsupportedOperationException.class)
diff --git a/scriptbuilder/src/test/resources/test_adminaccess_flipped.sh b/scriptbuilder/src/test/resources/test_adminaccess_flipped.sh
index d065eff..bfe8478 100644
--- a/scriptbuilder/src/test/resources/test_adminaccess_flipped.sh
+++ b/scriptbuilder/src/test/resources/test_adminaccess_flipped.sh
@@ -4,7 +4,7 @@
 %wheel ALL = (ALL) NOPASSWD:ALL
 END_OF_FILE
 chmod 0440 /etc/sudoers
-mkdir -p /home/users/defaultAdminUsername
+mkdir -p /home/users
 groupadd -f wheel
 useradd -s /bin/bash -g wheel -d /home/users/defaultAdminUsername -p 'crypt(0)' defaultAdminUsername
 mkdir -p /home/users/defaultAdminUsername/.ssh
diff --git a/scriptbuilder/src/test/resources/test_adminaccess_params.sh b/scriptbuilder/src/test/resources/test_adminaccess_params.sh
index 30fd511..3c33878 100644
--- a/scriptbuilder/src/test/resources/test_adminaccess_params.sh
+++ b/scriptbuilder/src/test/resources/test_adminaccess_params.sh
@@ -3,7 +3,7 @@
 	%wheel ALL = (ALL) NOPASSWD:ALL
 END_OF_JCLOUDS_FILE
 chmod 0440 /etc/sudoers
-mkdir -p /over/ridden/foo
+mkdir -p /over/ridden
 groupadd -f wheel
 useradd -s /bin/bash -g wheel -m  -d /over/ridden/foo -p 'crypt(bar)' foo
 mkdir -p /over/ridden/foo/.ssh
diff --git a/scriptbuilder/src/test/resources/test_adminaccess_plainuser.sh b/scriptbuilder/src/test/resources/test_adminaccess_plainuser.sh
index e63f6f2..68325f9 100644
--- a/scriptbuilder/src/test/resources/test_adminaccess_plainuser.sh
+++ b/scriptbuilder/src/test/resources/test_adminaccess_plainuser.sh
@@ -1,4 +1,4 @@
-mkdir -p /home/users/defaultAdminUsername
+mkdir -p /home/users
 useradd -s /bin/bash -m  -d /home/users/defaultAdminUsername -p 'crypt(0)' defaultAdminUsername
 mkdir -p /home/users/defaultAdminUsername/.ssh
 cat >> /home/users/defaultAdminUsername/.ssh/authorized_keys <<-'END_OF_JCLOUDS_FILE'
diff --git a/scriptbuilder/src/test/resources/test_adminaccess_standard.sh b/scriptbuilder/src/test/resources/test_adminaccess_standard.sh
index 6c48413..28d1849 100644
--- a/scriptbuilder/src/test/resources/test_adminaccess_standard.sh
+++ b/scriptbuilder/src/test/resources/test_adminaccess_standard.sh
@@ -3,7 +3,7 @@
 	%wheel ALL = (ALL) NOPASSWD:ALL
 END_OF_JCLOUDS_FILE
 chmod 0440 /etc/sudoers
-mkdir -p /home/users/defaultAdminUsername
+mkdir -p /home/users
 groupadd -f wheel
 useradd -s /bin/bash -g wheel -m  -d /home/users/defaultAdminUsername -p 'crypt(0)' defaultAdminUsername
 mkdir -p /home/users/defaultAdminUsername/.ssh