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

This commit is contained in:
adriancole 2013-05-15 12:05:57 -07:00
parent b021d743fd
commit f88609d1dd
13 changed files with 338 additions and 9 deletions

View File

@ -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>();
}

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {
/**

View File

@ -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 {

View File

@ -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 transactions 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);
}

View File

@ -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 {

View File

@ -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");
}
}

View File

@ -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);
}
}
}

View File

@ -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>