/**
* Copyright (C) 2000-2016 Atomikos <info@atomikos.com>
*
* LICENSE CONDITIONS
*
* See http://www.atomikos.com/Main/WhichLicenseApplies for details.
*/
package com.atomikos.icatch.tcc.rest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.Response;
import javax.xml.datatype.DatatypeConfigurationException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.atomikos.tcc.rest.ParticipantLink;
import com.atomikos.tcc.rest.Transaction;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
public class CoordinatorImpTestJUnit {
private static final String COORDINATOR_BASE_URL = "http://localhost:9001";
private static final String COORDINATOR_CLIENT_URL = COORDINATOR_BASE_URL;
private static final String PARTICIPANT_SERVER_URL_1 = "http://localhost:9002";
private static final String PARTICIPANT_CLIENT_URL_1 = PARTICIPANT_SERVER_URL_1
+ "/participant";
private static final String PARTICIPANT_SERVER_URL_2 = "http://localhost:9003";
private static final String PARTICIPANT_CLIENT_URL_2 = PARTICIPANT_SERVER_URL_2
+ "/participant";
private static WebTarget coordinator;
private static TestParticipant tp1, tp2;
private static Server server;
@BeforeClass
public static void beforeClass() throws Exception {
server = new Server(COORDINATOR_BASE_URL);
server.start();
coordinator = getCoordinatorForJson();
tp1 = new TestParticipant();
tp1.export(PARTICIPANT_SERVER_URL_1);
tp2 = new TestParticipant();
tp2.export(PARTICIPANT_SERVER_URL_2);
}
private static void stop() {
server.stop(true);
tp1.stop();
tp2.stop();
}
@AfterClass
public static void afterClass() {
stop();
}
private static WebTarget getCoordinatorForJson() {
Client client = ClientBuilder.newClient();
client.register(new JacksonJaxbJsonProvider());
client.register(new TransactionProvider());
WebTarget target = client.target(COORDINATOR_CLIENT_URL);
return target.path("/coordinator");
}
public static void main(String[] args) throws DatatypeConfigurationException, IOException {
Transaction transaction = createTransaction("2002-05-30T09:30:10Z", "http://1","http://2");
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.writeTo(transaction,
Transaction.class, Transaction.class,
null, MediaType.APPLICATION_JSON_TYPE,
new MultivaluedHashMap<String, Object>(),
System.out);
}
private static Transaction createTransaction(String date,
String... participantUris) throws DatatypeConfigurationException {
List<ParticipantLink> participants = new ArrayList<ParticipantLink>();
for (String uri : participantUris) {
ParticipantLink participantLink = new ParticipantLink(uri,date);
participants.add(participantLink);
}
Transaction transaction = new Transaction();
transaction.getParticipantLinks().addAll(participants);
return transaction;
}
private static Transaction createTransaction(long timestamp,
String... participantUris) throws DatatypeConfigurationException {
List<ParticipantLink> participants = new ArrayList<ParticipantLink>();
for (String uri : participantUris) {
ParticipantLink participantLink = new ParticipantLink(uri,timestamp);
participants.add(participantLink);
}
Transaction transaction = new Transaction();
transaction.getParticipantLinks().addAll(participants);
return transaction;
}
@Before
public void setUp() {
tp1.reset();
tp2.reset();
}
@Test
public void testJsonCancelWorksForNonExistentParticipant()
throws DatatypeConfigurationException, InterruptedException {
Transaction transaction = createTransaction(System.currentTimeMillis(),
"http://www.example.com");
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
}
@Test
public void testJsonCancelWorksForEmptyTransaction() {
Transaction transaction = new Transaction();
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
}
@Test
public void testJsonConfirmWorksForEmptyTransaction() {
Transaction transaction = new Transaction();
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
}
@Test
public void testJsonCancelCallsDeleteOnEachParticipant()
throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
assertTrue(tp1.wasCancelled());
assertTrue(tp2.wasCancelled());
}
@Test
public void testJsonConfirmCallsOptionsOnEachParticipant()
throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertTrue(tp1.wasOptionsCalled());
assertTrue(tp2.wasOptionsCalled());
}
@Test
public void testFailureOnOptionsDoesNotPreventConfirm()
throws DatatypeConfigurationException {
tp1.setFailOnOptions();
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertTrue(tp1.wasConfirmed());
}
@Test
public void testJsonConfirmCallsPutOnEachParticipant()
throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertTrue(tp1.wasConfirmed());
assertTrue(tp2.wasConfirmed());
}
@Test
public void testJsonConfirmAfterGlobalTimeoutThrows404()
throws DatatypeConfigurationException {
tp1.setSimulateTimeout();
tp2.setSimulateTimeout();
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
Response r = coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(404, r.getStatus());
}
@Test
public void testJsonConfirmAfterPartialTimeoutThrows409()
throws DatatypeConfigurationException {
tp1.setSimulateTimeout();
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
Response r = coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(409, r.getStatus());
}
private long createSufficientExpiryDateToAvoidIntermediateTimeout() {
return System.currentTimeMillis() + 5000;
}
@Test
public void testConfirmIsIdempotent() throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
}
@Test
public void testCancelIsIdempotent() throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
}
@Test
public void testTimeoutMeansCancel() throws DatatypeConfigurationException {
Transaction transaction = createTransaction(
System.currentTimeMillis(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
Response r =coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(404, r.getStatus());
assertEquals("transaction has timed out and was cancelled", getMessage(r));
}
@Test
public void testParticipantLinkUriMissingThrows() throws Exception {
Transaction transaction = createTransaction(1000L,new String []{ null});
Response r =coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(400, r.getStatus());
assertEquals("each participantLink must have a value for 'uri'", getMessage(r));
}
@Test
public void testParticipantMalformedExpiresThrows() throws Exception {
String invalidISO8601Date = "participantLink: expires must be a valid ISO 8601 date";
Transaction transaction = createTransaction(invalidISO8601Date, "DUMMY");
Response r =coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(400, r.getStatus());
assertEquals("invalid date format for participantLink 'expires': "+invalidISO8601Date, getMessage(r));
}
@Test
public void testParticipantExpiresMissingThrows() throws Exception {
Transaction transaction = createTransaction(null, "DUMMY");
Response r =coordinator.path("/confirm").request().put(Entity.entity(transaction, "application/tcc+json"));
assertEquals(400, r.getStatus());
assertEquals("each participantLink must have an 'expires'", getMessage(r));
}
private String getMessage(Response r) {
InputStream in = (InputStream) r.getEntity();
String message = new Scanner(in,"UTF-8").useDelimiter("\\A").next();
return message;
}
@Test
public void testJsonCancelIgnoresParticipantFailures() throws Exception {
tp1.setSimulateErrorOnCancel();
Transaction transaction = createTransaction(
createSufficientExpiryDateToAvoidIntermediateTimeout(), PARTICIPANT_CLIENT_URL_1,
PARTICIPANT_CLIENT_URL_2);
coordinator.path("/cancel").request().put(Entity.entity(transaction, "application/tcc+json"));
}
}