You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ad...@apache.org on 2013/05/15 22:36:47 UTC

[1/2] git commit: [JCLOUDS-43] add scoped transaction support to ultradns-ws

Updated Branches:
  refs/heads/1.6.x 97fdc2655 -> 60388a6ed


[JCLOUDS-43] add scoped transaction support to ultradns-ws


Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/commit/60388a6e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/tree/60388a6e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/diff/60388a6e

Branch: refs/heads/1.6.x
Commit: 60388a6ed5faeaf5c0a691b6aebe75a956f5ef5e
Parents: d348634
Author: adriancole <ad...@gmail.com>
Authored: Wed May 15 12:05:57 2013 -0700
Committer: adriancole <ad...@gmail.com>
Committed: Wed May 15 13:33:50 2013 -0700

----------------------------------------------------------------------
 .../org/jclouds/ultradns/ws/ScopedTransaction.java |  134 +++++++++++++++
 .../jclouds/ultradns/ws/UltraDNSWSApiMetadata.java |    7 +-
 .../ultradns/ws/features/DirectionalGroupApi.java  |    6 +-
 .../ultradns/ws/features/DirectionalPoolApi.java   |    6 +-
 .../ultradns/ws/features/ResourceRecordApi.java    |    6 +-
 .../ultradns/ws/features/RoundRobinPoolApi.java    |    6 +-
 .../org/jclouds/ultradns/ws/features/TaskApi.java  |    6 +-
 .../ws/features/TrafficControllerPoolApi.java      |    6 +-
 .../ultradns/ws/features/TransactionApi.java       |   24 +++-
 .../org/jclouds/ultradns/ws/features/ZoneApi.java  |    6 +-
 .../ultradns/ws/ScopedTransactionExpectTest.java   |   55 ++++++
 .../ultradns/ws/ScopedTransactionLiveTest.java     |   84 +++++++++
 .../src/test/resources/create_zone_tx.xml          |    1 +
 13 files changed, 338 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/ScopedTransaction.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/ScopedTransaction.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/ScopedTransaction.java
new file mode 100644
index 0000000..ea64816
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/ScopedTransaction.java
@@ -0,0 +1,134 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.ultradns.ws;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.rest.annotations.Transform;
+
+import com.google.common.base.Function;
+import com.google.inject.AbstractModule;
+
+/**
+ * Adds support for implicit transactions.
+ * 
+ * @author Adrian Cole
+ */
+public class ScopedTransaction {
+
+   /**
+    * Sets a scoped transaction. Add to rest calls that start a transaction.
+    * Make sure that these note a possible IllegalStateException when a
+    * transaction is already in progress.
+    * 
+    * <p/>
+    * 
+    * ex.
+    * 
+    * <pre>
+    * ...
+    * @Transform(ScopedTransaction.Set.class)
+    * String start() throws IllegalStateException;
+    * </pre>
+    * 
+    * @see Transform
+    */
+   public static class Set implements Function<String, String> {
+      private final ScopedTransaction scope;
+
+      @Inject
+      private Set(ScopedTransaction scope) {
+         this.scope = scope;
+      }
+
+      @Override
+      public String apply(String in) {
+         checkState(scope.transactionId.get() == null, "A transaction is already in progress");
+         scope.transactionId.set(in);
+         return in;
+      }
+   }
+
+   /**
+    * Removes a scoped transaction, if present. Add to rest calls that commit or
+    * rollback a transaction.
+    * 
+    * <p/>
+    * 
+    * ex.
+    * 
+    * <pre>
+    * ...
+    * @Transform(ScopedTransaction.Invalidate.class)
+    * void rollback(String txId);
+    * </pre>
+    * 
+    * @see Transform
+    */
+   public static class Remove implements Function<Void, Void> {
+      private final ScopedTransaction scope;
+
+      @Inject
+      private Remove(ScopedTransaction scope) {
+         this.scope = scope;
+      }
+
+      @Override
+      public Void apply(Void in) {
+         scope.transactionId.remove();
+         return in;
+      }
+   }
+
+   public static class Filter implements HttpRequestFilter {
+      private final ScopedTransaction scope;
+
+      @Inject
+      private Filter(ScopedTransaction scope) {
+         this.scope = scope;
+      }
+
+      @Override
+      public HttpRequest filter(HttpRequest request) throws HttpException {
+         String transactionId = scope.transactionId.get();
+         if (transactionId == null)
+            return request;
+         String body = request.getPayload().getRawContent().toString();
+         body = body.replace("<transactionID />", format("<transactionID>%s</transactionID>", transactionId));
+         return request.toBuilder().payload(body).build();
+      }
+   }
+
+   /**
+    * In order to support implicit transactions, this module must be installed.
+    */
+   public static class Module extends AbstractModule {
+      public void configure() {
+         ScopedTransaction scope = new ScopedTransaction();
+         bind(ScopedTransaction.class).toInstance(scope);
+      }
+   }
+
+   private final ThreadLocal<String> transactionId = new ThreadLocal<String>();
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApiMetadata.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApiMetadata.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApiMetadata.java
index f0131fd..655271c 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApiMetadata.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApiMetadata.java
@@ -25,6 +25,9 @@ import org.jclouds.apis.ApiMetadata;
 import org.jclouds.rest.internal.BaseHttpApiMetadata;
 import org.jclouds.ultradns.ws.config.UltraDNSWSHttpApiModule;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
 /**
  * Implementation of {@link ApiMetadata} for Neustar's UltraDNSWS api.
  * 
@@ -61,7 +64,9 @@ public class UltraDNSWSApiMetadata extends BaseHttpApiMetadata<UltraDNSWSApi> {
          .documentation(URI.create("https://portal.ultradns.com/static/docs/NUS_API_XML_SOAP.pdf"))
          .defaultEndpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
          .defaultProperties(UltraDNSWSApiMetadata.defaultProperties())
-         .defaultModule(UltraDNSWSHttpApiModule.class);
+         .defaultModules(ImmutableSet.<Class<? extends Module>> builder()
+                                     .add(UltraDNSWSHttpApiModule.class)
+                                     .add(ScopedTransaction.Module.class).build());
       }
 
       @Override

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
index b8b2620..f66e88f 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalGroupApi.java
@@ -32,6 +32,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.binders.DirectionalGroupCoordinatesToXML;
 import org.jclouds.ultradns.ws.domain.AccountLevelGroup;
 import org.jclouds.ultradns.ws.domain.DirectionalGroup;
@@ -51,7 +52,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface DirectionalGroupApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
index 1b7457a..6bf0edf 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/DirectionalPoolApi.java
@@ -33,6 +33,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSApi;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.DirectionalGroupOverlapException;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
@@ -55,7 +56,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface DirectionalPoolApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ResourceRecordApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ResourceRecordApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ResourceRecordApi.java
index f636b53..6351f2a 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ResourceRecordApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ResourceRecordApi.java
@@ -30,6 +30,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
 import org.jclouds.ultradns.ws.binders.ZoneAndResourceRecordToXML;
 import org.jclouds.ultradns.ws.domain.ResourceRecord;
@@ -45,7 +46,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface ResourceRecordApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/RoundRobinPoolApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/RoundRobinPoolApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/RoundRobinPoolApi.java
index 74590b8..e9aecf0 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/RoundRobinPoolApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/RoundRobinPoolApi.java
@@ -30,6 +30,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
 import org.jclouds.ultradns.ws.domain.ResourceRecord;
 import org.jclouds.ultradns.ws.domain.ResourceRecordDetail;
@@ -47,7 +48,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface RoundRobinPoolApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TaskApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TaskApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TaskApi.java
index a378ba6..80935f0 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TaskApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TaskApi.java
@@ -30,6 +30,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.domain.Task;
 import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
 import org.jclouds.ultradns.ws.xml.ElementTextHandler;
@@ -43,7 +44,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface TaskApi {
    /**

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TrafficControllerPoolApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TrafficControllerPoolApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TrafficControllerPoolApi.java
index a257391..605bf97 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TrafficControllerPoolApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TrafficControllerPoolApi.java
@@ -33,6 +33,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
 import org.jclouds.ultradns.ws.binders.UpdatePoolRecordToXML;
 import org.jclouds.ultradns.ws.domain.PoolRecordSpec;
@@ -54,7 +55,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface TrafficControllerPoolApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
index 0a555ea..685390e 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
@@ -26,13 +26,30 @@ import org.jclouds.rest.annotations.Fallback;
 import org.jclouds.rest.annotations.Payload;
 import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.Transform;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
 import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
 import org.jclouds.ultradns.ws.xml.ElementTextHandler;
 
 /**
+ * Adds transaction support when performing multiple write commands.
+ * 
+ * <p/>
+ * ex.
+ * 
+ * <pre>
+ * String txId = ultraDNSApi.getTransactionApi().start();
+ * try {
+ *    // perform operations
+ *    ultraDNSApi.getTransactionApi().commit(txId);
+ * } catch (Throwable t) {
+ *    ultraDNSApi.getTransactionApi().rollback(txId);
+ *    throw propagate(t);
+ * }
+ * </pre>
  * 
  * @see <a href="https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01?wsdl" />
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
@@ -49,12 +66,15 @@ public interface TransactionApi {
     * @return id of the transaction created
     * @throws TooManyTransactionsException
     *            if the maximum concurrent exception limit was hit.
+    * @throws IllegalStateException
+    *            if another transaction is in progress.
     */
    @Named("startTransaction")
    @POST
    @XMLResponseParser(ElementTextHandler.TransactionID.class)
    @Payload("<v01:startTransaction/>")
-   String start() throws TooManyTransactionsException;
+   @Transform(ScopedTransaction.Set.class)
+   String start() throws TooManyTransactionsException, IllegalStateException;
 
    /**
     * This request commits all of a transaction’s requests and writes them to
@@ -68,6 +88,7 @@ public interface TransactionApi {
    @Named("commitTransaction")
    @POST
    @Payload("<v01:commitTransaction><transactionID>{transactionID}</transactionID></v01:commitTransaction>")
+   @Transform(ScopedTransaction.Remove.class)
    void commit(@PayloadParam("transactionID") String transactionID) throws ResourceNotFoundException;
 
    /**
@@ -81,5 +102,6 @@ public interface TransactionApi {
    @POST
    @Payload("<v01:rollbackTransaction><transactionID>{transactionID}</transactionID></v01:rollbackTransaction>")
    @Fallback(VoidOnNotFoundOr404.class)
+   @Transform(ScopedTransaction.Remove.class)
    void rollback(@PayloadParam("transactionID") String transactionID);
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ZoneApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ZoneApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ZoneApi.java
index 5466e43..cfee697 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ZoneApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/ZoneApi.java
@@ -31,6 +31,7 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.VirtualHost;
 import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.ScopedTransaction;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
 import org.jclouds.ultradns.ws.domain.Zone;
 import org.jclouds.ultradns.ws.domain.Zone.Type;
@@ -46,7 +47,10 @@ import com.google.common.collect.FluentIterable;
  * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
  * @author Adrian Cole
  */
-@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@RequestFilters({
+   ScopedTransaction.Filter.class,
+   SOAPWrapWithPasswordAuth.class
+})
 @VirtualHost
 public interface ZoneApi {
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionExpectTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionExpectTest.java
new file mode 100644
index 0000000..0940974
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionExpectTest.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.ultradns.ws;
+import static com.google.common.net.HttpHeaders.HOST;
+import static javax.ws.rs.HttpMethod.POST;
+import static javax.ws.rs.core.Response.Status.OK;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.ultradns.ws.UltraDNSWSApi;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiExpectTest;
+import org.testng.annotations.Test;
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "ScopedTransactionExpectTest")
+public class ScopedTransactionExpectTest extends BaseUltraDNSWSApiExpectTest {
+   HttpRequest start = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/start_tx.xml", "application/xml")).build();
+
+   HttpResponse startResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+         .payload(payloadFromResourceWithContentType("/tx_started.xml", "application/xml")).build();
+
+   HttpRequest createWithTx = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/create_zone_tx.xml", "application/xml")).build();
+
+   HttpResponse createResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+         .payload(payloadFromResourceWithContentType("/zone_created.xml", "application/xml")).build();
+
+   public void testAddTransactionIdWhenTransactionStarted() {
+      UltraDNSWSApi success = requestsSendResponses(start, startResponse, createWithTx, createResponse);
+
+      success.getTransactionApi().start();
+      success.getZoneApi().createInAccount("jclouds.org.", "AAAAAAAAAAAAAAAA");
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionLiveTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionLiveTest.java
new file mode 100644
index 0000000..16c2398
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/ScopedTransactionLiveTest.java
@@ -0,0 +1,84 @@
+/**
+ * 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.ultradns.ws;
+
+import static java.util.logging.Logger.getAnonymousLogger;
+import static org.jclouds.ultradns.ws.domain.ResourceRecord.rrBuilder;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import org.jclouds.ultradns.ws.domain.ResourceRecord;
+import org.jclouds.ultradns.ws.domain.ZoneProperties;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "live", testName = "ScopedTransactionLiveTest")
+public class ScopedTransactionLiveTest extends BaseUltraDNSWSApiLiveTest {
+
+   ResourceRecord mx = rrBuilder().name("mail." + zoneName)
+         .type(15)
+         .ttl(1800)
+         .infoValue(10)
+         .infoValue("maileast.jclouds.org.").build();
+
+   @Test
+   public void testMultiStepTransactionOnCommit() {
+      String txId = api.getTransactionApi().start();
+      assertNotNull(txId);
+      getAnonymousLogger().info("starting transaction: " + txId);
+      try {
+         api.getZoneApi().createInAccount(zoneName, account.getId());
+         api.getResourceRecordApiForZone(zoneName).create(mx);
+
+         // can't read uncommitted stuff
+         assertNull(api.getZoneApi().get(zoneName));
+         
+         // commit the tx
+         api.getTransactionApi().commit(txId);
+
+         // now we can read it
+         ZoneProperties newZone = api.getZoneApi().get(zoneName);
+         assertEquals(newZone.getName(), zoneName);
+         assertEquals(newZone.getResourceRecordCount(), 6);
+      } finally {
+         // in case an assertion problem or otherwise occurred in the test.
+         api.getTransactionApi().rollback(txId);
+         api.getZoneApi().delete(zoneName);
+      }
+   }
+
+   @Test
+   public void testScopedTransactionOnRollback() {
+      String txId = api.getTransactionApi().start();
+      assertNotNull(txId);
+      getAnonymousLogger().info("starting transaction: " + txId);
+      try {
+         api.getZoneApi().createInAccount(zoneName, account.getId());
+         api.getTransactionApi().rollback(txId);
+         assertNull(api.getZoneApi().get(zoneName));
+      } finally {
+         // in case an assertion problem or otherwise occurred in the test.
+         api.getZoneApi().delete(zoneName);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/60388a6e/providers/ultradns-ws/src/test/resources/create_zone_tx.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/create_zone_tx.xml b/providers/ultradns-ws/src/test/resources/create_zone_tx.xml
new file mode 100644
index 0000000..ba40474
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/create_zone_tx.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:createPrimaryZone><transactionID>jclouds-37562</transactionID><accountId>AAAAAAAAAAAAAAAA</accountId><zoneName>jclouds.org.</zoneName><forceImport>false</forceImport></v01:createPrimaryZone></soapenv:Body></soapenv:Envelope>
\ No newline at end of file


[2/2] git commit: [JCLOUDS-43] add TransactionApi and tests to ultradns-ws provider

Posted by ad...@apache.org.
[JCLOUDS-43] add TransactionApi and tests to ultradns-ws provider


Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/commit/d3486342
Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/tree/d3486342
Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/diff/d3486342

Branch: refs/heads/1.6.x
Commit: d3486342e0ace65862fcdd79a20402093c9b10f4
Parents: 97fdc26
Author: adriancole <ad...@gmail.com>
Authored: Wed May 15 10:16:43 2013 -0700
Committer: adriancole <ad...@gmail.com>
Committed: Wed May 15 13:33:50 2013 -0700

----------------------------------------------------------------------
 .../org/jclouds/ultradns/ws/UltraDNSWSApi.java     |    7 +
 .../jclouds/ultradns/ws/UltraDNSWSExceptions.java  |   13 ++
 .../ultradns/ws/features/TransactionApi.java       |   85 +++++++++++++
 .../ws/handlers/UltraDNSWSErrorHandler.java        |   12 ++
 .../ultradns/ws/xml/ElementTextHandler.java        |    6 +
 .../ws/features/TransactionApiExpectTest.java      |   99 +++++++++++++++
 .../ws/features/TransactionApiLiveTest.java        |   78 ++++++++++++
 .../ws/handlers/UltraDNSWSErrorHandlerTest.java    |   49 +++++++
 .../ultradns-ws/src/test/resources/commit_tx.xml   |    1 +
 .../ultradns-ws/src/test/resources/rollback_tx.xml |    1 +
 .../ultradns-ws/src/test/resources/start_tx.xml    |    1 +
 .../src/test/resources/tx_committed.xml            |    8 ++
 .../src/test/resources/tx_doesnt_exist.xml         |   16 +++
 .../src/test/resources/tx_rolledback.xml           |    8 ++
 .../ultradns-ws/src/test/resources/tx_started.xml  |    8 ++
 .../ultradns-ws/src/test/resources/tx_toomany.xml  |   16 +++
 16 files changed, 408 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
index 7753a7b..647acd8 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSApi.java
@@ -36,6 +36,7 @@ import org.jclouds.ultradns.ws.features.ResourceRecordApi;
 import org.jclouds.ultradns.ws.features.RoundRobinPoolApi;
 import org.jclouds.ultradns.ws.features.TaskApi;
 import org.jclouds.ultradns.ws.features.TrafficControllerPoolApi;
+import org.jclouds.ultradns.ws.features.TransactionApi;
 import org.jclouds.ultradns.ws.features.ZoneApi;
 import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
 import org.jclouds.ultradns.ws.xml.AccountHandler;
@@ -127,4 +128,10 @@ public interface UltraDNSWSApi extends Closeable {
     */
    @Delegate
    TaskApi getTaskApi();
+
+   /**
+    * Provides access to Transaction features.
+    */
+   @Delegate
+   TransactionApi getTransactionApi();
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSExceptions.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSExceptions.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSExceptions.java
index 72afc86..08d7f79 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSExceptions.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/UltraDNSWSExceptions.java
@@ -18,6 +18,8 @@
  */
 package org.jclouds.ultradns.ws;
 
+import org.jclouds.rest.InsufficientResourcesException;
+
 /**
  * Exceptions likely to be encountered when using {@link UltraDNSWSApi}
  * 
@@ -46,4 +48,15 @@ public interface UltraDNSWSExceptions {
          super(message, cause);
       }
    }
+
+   /**
+    * Error 9010: Ultra API only allows 3 concurrent transactions per user
+    */
+   public static class TooManyTransactionsException extends InsufficientResourcesException {
+      private static final long serialVersionUID = 1L;
+
+      public TooManyTransactionsException(String message, Throwable cause) {
+         super(message, cause);
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
new file mode 100644
index 0000000..0a555ea
--- /dev/null
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/features/TransactionApi.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.ultradns.ws.features;
+
+import javax.inject.Named;
+import javax.ws.rs.POST;
+
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Payload;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.VirtualHost;
+import org.jclouds.rest.annotations.XMLResponseParser;
+import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
+import org.jclouds.ultradns.ws.filters.SOAPWrapWithPasswordAuth;
+import org.jclouds.ultradns.ws.xml.ElementTextHandler;
+
+/**
+ * 
+ * @see <a href="https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01?wsdl" />
+ * @see <a href="https://www.ultradns.net/api/NUS_API_XML_SOAP.pdf" />
+ * @author Adrian Cole
+ */
+@RequestFilters(SOAPWrapWithPasswordAuth.class)
+@VirtualHost
+public interface TransactionApi {
+   /**
+    * Starts a transaction, if possible. Note that 3 simultaneous ones are
+    * allowed per account, and they have a 1 hr timeout. All write commands will
+    * use this transaction until delete is called.
+    * 
+    * @return id of the transaction created
+    * @throws TooManyTransactionsException
+    *            if the maximum concurrent exception limit was hit.
+    */
+   @Named("startTransaction")
+   @POST
+   @XMLResponseParser(ElementTextHandler.TransactionID.class)
+   @Payload("<v01:startTransaction/>")
+   String start() throws TooManyTransactionsException;
+
+   /**
+    * This request commits all of a transaction’s requests and writes them to
+    * the Neustar Ultra Services
+    * 
+    * @param transactionID
+    *           transaction id to commit.
+    * @throws ResourceNotFoundException
+    *            if the transaction has already been committed or never existed.
+    */
+   @Named("commitTransaction")
+   @POST
+   @Payload("<v01:commitTransaction><transactionID>{transactionID}</transactionID></v01:commitTransaction>")
+   void commit(@PayloadParam("transactionID") String transactionID) throws ResourceNotFoundException;
+
+   /**
+    * This request rolls back any changes included in a transaction. This will
+    * not error, if the transaction has timed out or does not exist.
+    * 
+    * @param transactionID
+    *           transaction id to rollback.
+    */
+   @Named("rollbackTransaction")
+   @POST
+   @Payload("<v01:rollbackTransaction><transactionID>{transactionID}</transactionID></v01:rollbackTransaction>")
+   @Fallback(VoidOnNotFoundOr404.class)
+   void rollback(@PayloadParam("transactionID") String transactionID);
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
index 647db52..7aa7e49 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandler.java
@@ -33,6 +33,7 @@ import org.jclouds.rest.ResourceNotFoundException;
 import org.jclouds.ultradns.ws.UltraDNSWSError;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.DirectionalGroupOverlapException;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
+import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
 import org.jclouds.ultradns.ws.UltraDNSWSResponseException;
 import org.jclouds.ultradns.ws.xml.UltraWSExceptionHandler;
 
@@ -80,6 +81,10 @@ public class UltraDNSWSErrorHandler implements HttpErrorHandler {
    static final class ErrorCodes {
       static final int UNKNOWN = 0;
       /**
+       * No transaction with Id Y found for the user Y
+       */
+      static final int TX_NOT_FOUND = 1602;
+      /**
        * Zone does not exist in the system.
        */
       static final int ZONE_NOT_FOUND = 1801;
@@ -131,6 +136,10 @@ public class UltraDNSWSErrorHandler implements HttpErrorHandler {
        * Geolocation/Source IP overlap(s) found
        */
       static final int DIRECTIONALPOOL_OVERLAP = 7021;
+      /**
+       * Ultra API only allows 3 concurrent transactions per user
+       */
+      static final int EXCEEDED_TX_LIMIT = 9010;
    }
 
    private Exception refineException(UltraDNSWSResponseException exception) {
@@ -141,6 +150,7 @@ public class UltraDNSWSErrorHandler implements HttpErrorHandler {
             return exception;
          if (exception.getError().getDescription().get().indexOf("Cannot find") == -1)
             return exception;
+      case ErrorCodes.TX_NOT_FOUND:
       case ErrorCodes.ZONE_NOT_FOUND:
       case ErrorCodes.RESOURCE_RECORD_NOT_FOUND:
       case ErrorCodes.ACCOUNT_NOT_FOUND:
@@ -157,6 +167,8 @@ public class UltraDNSWSErrorHandler implements HttpErrorHandler {
          return new ResourceAlreadyExistsException(message, exception);
       case ErrorCodes.DIRECTIONALPOOL_OVERLAP:
          return new DirectionalGroupOverlapException(message, exception);
+      case ErrorCodes.EXCEEDED_TX_LIMIT:
+         return new TooManyTransactionsException(message, exception);
       }
       return exception;
    }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ElementTextHandler.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ElementTextHandler.java b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ElementTextHandler.java
index 66fc90c..392d3b9 100644
--- a/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ElementTextHandler.java
+++ b/providers/ultradns-ws/src/main/java/org/jclouds/ultradns/ws/xml/ElementTextHandler.java
@@ -66,6 +66,12 @@ public abstract class ElementTextHandler extends ParseSax.HandlerForGeneratedReq
       }
    }
 
+   public static class TransactionID extends ElementTextHandler {
+      public TransactionID() {
+         super("transactionId");
+      }
+   }
+
    private final String textElement;
 
    private StringBuilder currentText = new StringBuilder();

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiExpectTest.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiExpectTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiExpectTest.java
new file mode 100644
index 0000000..5901967
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiExpectTest.java
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.ultradns.ws.features;
+import static com.google.common.net.HttpHeaders.HOST;
+import static javax.ws.rs.HttpMethod.POST;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static javax.ws.rs.core.Response.Status.OK;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.ultradns.ws.UltraDNSWSApi;
+import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiExpectTest;
+import org.testng.annotations.Test;
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "TransactionApiExpectTest")
+public class TransactionApiExpectTest extends BaseUltraDNSWSApiExpectTest {
+   HttpRequest start = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/start_tx.xml", "application/xml")).build();
+
+   HttpResponse startResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+         .payload(payloadFromResourceWithContentType("/tx_started.xml", "application/xml")).build();
+
+   public void testStartWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(start, startResponse);
+
+      assertEquals(success.getTransactionApi().start().toString(), "jclouds-37562");
+   }
+
+   HttpResponse tooManyResponse = HttpResponse.builder().message("Server Error").statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+         .payload(payloadFromResource("/tx_toomany.xml")).build();
+   
+   @Test(expectedExceptions = TooManyTransactionsException.class, expectedExceptionsMessageRegExp = "Ultra API only allows 3 concurrent transactions per user")
+   public void testStartWhenResponseError9010() {
+      UltraDNSWSApi tooMany = requestSendsResponse(start, tooManyResponse);
+      tooMany.getTransactionApi().start();
+   }
+
+   HttpRequest commit = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/commit_tx.xml", "application/xml")).build();
+
+   HttpResponse commitResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+         .payload(payloadFromResourceWithContentType("/tx_committed.xml", "application/xml")).build();
+
+   public void testCommitWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(commit, commitResponse);
+      success.getTransactionApi().commit("jclouds-37562");
+   }
+   
+   HttpResponse txDoesntExist = HttpResponse.builder().message("Server Error").statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+         .payload(payloadFromResource("/tx_doesnt_exist.xml")).build();
+
+   @Test(expectedExceptions = ResourceNotFoundException.class, expectedExceptionsMessageRegExp = "No transaction with Id AAAAAAAAAAAAAAAA found for the user .*")
+   public void testCommitWhenResponseError1602() {
+      UltraDNSWSApi notFound = requestSendsResponse(commit, txDoesntExist);
+      notFound.getTransactionApi().commit("jclouds-37562");
+   }
+
+   HttpRequest rollback = HttpRequest.builder().method(POST)
+         .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+         .addHeader(HOST, "ultra-api.ultradns.com:8443")
+         .payload(payloadFromResourceWithContentType("/rollback_tx.xml", "application/xml")).build();
+
+   HttpResponse rollbackResponse = HttpResponse.builder().statusCode(OK.getStatusCode())
+         .payload(payloadFromResourceWithContentType("/tx_rolledback.xml", "application/xml")).build();
+
+   public void testRollbackWhenResponseIs2xx() {
+      UltraDNSWSApi success = requestSendsResponse(rollback, rollbackResponse);
+      success.getTransactionApi().rollback("jclouds-37562");
+   }
+
+   public void testRollbackWhenResponseError1602IsOK() {
+      UltraDNSWSApi notFound = requestSendsResponse(rollback, txDoesntExist);
+      notFound.getTransactionApi().rollback("jclouds-37562");
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiLiveTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiLiveTest.java
new file mode 100644
index 0000000..74d8973
--- /dev/null
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/features/TransactionApiLiveTest.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.ultradns.ws.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
+import org.jclouds.ultradns.ws.internal.BaseUltraDNSWSApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Lists;
+
+/**
+ * @author Adrian Cole
+ */
+@Test(groups = "live", singleThreaded = true, testName = "TransactionApiLiveTest")
+public class TransactionApiLiveTest extends BaseUltraDNSWSApiLiveTest {
+
+   @Test
+   public void commitTransaction() {
+      String tx = api().start();
+      assertNotNull(tx);
+      api().commit(tx);
+   }
+
+   @Test
+   public void rollbackTransaction() {
+      String tx = api().start();
+      assertNotNull(tx);
+      api().rollback(tx);
+   }
+
+   @Test(expectedExceptions = TooManyTransactionsException.class, expectedExceptionsMessageRegExp = "Ultra API only allows 3 concurrent transactions per user")
+   public void only3Transactions() {
+      List<String> txIds = Lists.newArrayList();
+      try {
+         while (true)
+            txIds.add(api().start());
+      } finally {
+         for (String tx : txIds)
+            api().rollback(tx);
+         assertEquals(txIds.size(), 3);
+      }
+   }
+
+   @Test(expectedExceptions = ResourceNotFoundException.class, expectedExceptionsMessageRegExp = "No transaction with Id AAAAAAAAAAAAAAAA found for the user .*")
+   public void commitTransactionWhenNotFound() {
+      api().commit("AAAAAAAAAAAAAAAA");
+   }
+
+   @Test
+   public void testRollbackTransactionWhenNotFound() {
+      api().rollback("AAAAAAAAAAAAAAAA");
+   }
+
+   protected TransactionApi api() {
+      return api.getTransactionApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
index 91d3c94..dc544da 100644
--- a/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
+++ b/providers/ultradns-ws/src/test/java/org/jclouds/ultradns/ws/handlers/UltraDNSWSErrorHandlerTest.java
@@ -35,6 +35,7 @@ import org.jclouds.io.Payload;
 import org.jclouds.rest.ResourceNotFoundException;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.DirectionalGroupOverlapException;
 import org.jclouds.ultradns.ws.UltraDNSWSExceptions.ResourceAlreadyExistsException;
+import org.jclouds.ultradns.ws.UltraDNSWSExceptions.TooManyTransactionsException;
 import org.jclouds.ultradns.ws.UltraDNSWSResponseException;
 import org.testng.annotations.Test;
 
@@ -365,6 +366,54 @@ public class UltraDNSWSErrorHandlerTest {
    }
 
    @Test
+   public void testCode1602SetsResourceNotFoundException() throws IOException {
+      HttpRequest request = HttpRequest.builder().method(POST)
+                                                 .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+                                                 .addHeader(HOST, "ultra-api.ultradns.com:8443")
+                                                 .payload(payloadFromResource("/delete_directionalrecord.xml")).build();
+      HttpCommand command = new HttpCommand(request);
+      HttpResponse response = HttpResponse.builder()
+                                          .message(INTERNAL_SERVER_ERROR.getReasonPhrase())
+                                          .statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+                                          .payload(payloadFromResource("/tx_doesnt_exist.xml")).build();
+
+      function.handleError(command, response);
+
+      assertEquals(command.getException().getClass(), ResourceNotFoundException.class);
+      assertEquals(command.getException().getMessage(), "No transaction with Id AAAAAAAAAAAAAAAA found for the user jclouds");
+
+      UltraDNSWSResponseException exception = UltraDNSWSResponseException.class.cast(command.getException().getCause());
+
+      assertEquals(exception.getMessage(), "Error 1602: No transaction with Id AAAAAAAAAAAAAAAA found for the user jclouds");
+      assertEquals(exception.getError().getDescription().get(), "No transaction with Id AAAAAAAAAAAAAAAA found for the user jclouds");
+      assertEquals(exception.getError().getCode(), 1602);
+   }
+   
+   @Test
+   public void testCode9010SetsTooManyTransactionsException() throws IOException {
+      HttpRequest request = HttpRequest.builder().method(POST)
+                                                 .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")
+                                                 .addHeader(HOST, "ultra-api.ultradns.com:8443")
+                                                 .payload(payloadFromResource("/create_directionalrecord_newgroup.xml")).build();
+      HttpCommand command = new HttpCommand(request);
+      HttpResponse response = HttpResponse.builder()
+                                          .message(INTERNAL_SERVER_ERROR.getReasonPhrase())
+                                          .statusCode(INTERNAL_SERVER_ERROR.getStatusCode())
+                                          .payload(payloadFromResource("/tx_toomany.xml")).build();
+
+      function.handleError(command, response);
+
+      assertEquals(command.getException().getClass(), TooManyTransactionsException.class);
+      assertEquals(command.getException().getMessage(), "Ultra API only allows 3 concurrent transactions per user");
+
+      UltraDNSWSResponseException exception = UltraDNSWSResponseException.class.cast(command.getException().getCause());
+
+      assertEquals(exception.getMessage(), "Error 9010: Ultra API only allows 3 concurrent transactions per user");
+      assertEquals(exception.getError().getDescription().get(), "Ultra API only allows 3 concurrent transactions per user");
+      assertEquals(exception.getError().getCode(), 9010);
+   }
+
+   @Test
    public void testCode7021SetsDirectionalGroupOverlapException() throws IOException {
       HttpRequest request = HttpRequest.builder().method(POST)
                                                  .endpoint("https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01")

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/commit_tx.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/commit_tx.xml b/providers/ultradns-ws/src/test/resources/commit_tx.xml
new file mode 100644
index 0000000..3db768c
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/commit_tx.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:commitTransaction><transactionID>jclouds-37562</transactionID></v01:commitTransaction></soapenv:Body></soapenv:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/rollback_tx.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/rollback_tx.xml b/providers/ultradns-ws/src/test/resources/rollback_tx.xml
new file mode 100644
index 0000000..188f37b
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/rollback_tx.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:rollbackTransaction><transactionID>jclouds-37562</transactionID></v01:rollbackTransaction></soapenv:Body></soapenv:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/start_tx.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/start_tx.xml b/providers/ultradns-ws/src/test/resources/start_tx.xml
new file mode 100644
index 0000000..58929f3
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/start_tx.xml
@@ -0,0 +1 @@
+<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v01="http://webservice.api.ultra.neustar.com/v01/"><soapenv:Header><wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><wsse:UsernameToken><wsse:Username>identity</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">credential</wsse:Password></wsse:UsernameToken></wsse:Security></soapenv:Header><soapenv:Body><v01:startTransaction/></soapenv:Body></soapenv:Envelope>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/tx_committed.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/tx_committed.xml b/providers/ultradns-ws/src/test/resources/tx_committed.xml
new file mode 100644
index 0000000..d5e5196
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/tx_committed.xml
@@ -0,0 +1,8 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:commitTransactionResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<result xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">Successful</result>
+		</ns1:commitTransactionResponse>
+	</soap:Body>
+</soap:Envelope>

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/tx_doesnt_exist.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/tx_doesnt_exist.xml b/providers/ultradns-ws/src/test/resources/tx_doesnt_exist.xml
new file mode 100644
index 0000000..4f52669
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/tx_doesnt_exist.xml
@@ -0,0 +1,16 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<soap:Fault>
+			<faultcode>soap:Server</faultcode>
+			<faultstring>Fault occurred while processing.</faultstring>
+			<detail>
+				<ns1:UltraWSException xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+					<errorCode xmlns:ns2="http://schema.ultraservice.neustar.com/v01/"
+						xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+						xsi:type="xs:int">1602</errorCode>
+					<errorDescription xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">No transaction with Id AAAAAAAAAAAAAAAA found for the user jclouds</errorDescription>
+				</ns1:UltraWSException>
+			</detail>
+		</soap:Fault>
+	</soap:Body>
+</soap:Envelope>

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/tx_rolledback.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/tx_rolledback.xml b/providers/ultradns-ws/src/test/resources/tx_rolledback.xml
new file mode 100644
index 0000000..7fd4515
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/tx_rolledback.xml
@@ -0,0 +1,8 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:rollbackTransactionResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<result xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">Successful</result>
+		</ns1:rollbackTransactionResponse>
+	</soap:Body>
+</soap:Envelope>

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/tx_started.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/tx_started.xml b/providers/ultradns-ws/src/test/resources/tx_started.xml
new file mode 100644
index 0000000..375ac0e
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/tx_started.xml
@@ -0,0 +1,8 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<ns1:startTransactionResponse
+			xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+			<transactionId xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">jclouds-37562</transactionId>
+		</ns1:startTransactionResponse>
+	</soap:Body>
+</soap:Envelope>

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/d3486342/providers/ultradns-ws/src/test/resources/tx_toomany.xml
----------------------------------------------------------------------
diff --git a/providers/ultradns-ws/src/test/resources/tx_toomany.xml b/providers/ultradns-ws/src/test/resources/tx_toomany.xml
new file mode 100644
index 0000000..229e181
--- /dev/null
+++ b/providers/ultradns-ws/src/test/resources/tx_toomany.xml
@@ -0,0 +1,16 @@
+<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+	<soap:Body>
+		<soap:Fault>
+			<faultcode>soap:Server</faultcode>
+			<faultstring>Fault occurred while processing.</faultstring>
+			<detail>
+				<ns1:UltraWSException xmlns:ns1="http://webservice.api.ultra.neustar.com/v01/">
+					<errorCode xmlns:ns2="http://schema.ultraservice.neustar.com/v01/"
+						xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+						xsi:type="xs:int">9010</errorCode>
+					<errorDescription xmlns:ns2="http://schema.ultraservice.neustar.com/v01/">Ultra API only allows 3 concurrent transactions per user</errorDescription>
+				</ns1:UltraWSException>
+			</detail>
+		</soap:Fault>
+	</soap:Body>
+</soap:Envelope>