com.atomikos.icatch.tcc.rest.CoordinatorImp Maven / Gradle / Ivy
/**
* Copyright (C) 2000-2017 Atomikos
*
* LICENSE CONDITIONS
*
* See http://www.atomikos.com/Main/WhichLicenseApplies for details.
*/
package com.atomikos.icatch.tcc.rest;
import java.util.Calendar;
import javax.ws.rs.Consumes;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.DatatypeConverter;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.HeurRollbackException;
import com.atomikos.icatch.RollbackException;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.tcc.rest.Coordinator;
import com.atomikos.tcc.rest.ParticipantLink;
import com.atomikos.tcc.rest.Transaction;
@Consumes({"application/tcc+json"})
@Path("/coordinator")
public class CoordinatorImp implements Coordinator {
private static Logger LOGGER = LoggerFactory.createLogger(CoordinatorImp.class);
private CompositeTransactionManager ctm;
public CoordinatorImp() {
}
private CompositeTransactionManager getCompositeTransactionManager() {
if (Configuration.getCompositeTransactionManager() == null) {
LOGGER.logWarning("Transaction core not yet initialized - initializing now, but should be done before!");
Configuration.init();
}
if (ctm == null) {
ctm = Configuration.getCompositeTransactionManager();
}
return ctm;
}
private CompositeTransaction createTaasTransaction(Transaction transaction) {
CompositeTransaction ct = null;
try {
ct = convertToCompositeTransaction(transaction);
} catch (Exception e) {
LOGGER.logError("Unexpected error while creating transaction", e);
throw new WebApplicationException(500);
}
return ct;
}
private CompositeTransaction convertToCompositeTransaction(Transaction transaction) {
long timeout = deriveTimeout(transaction);
CompositeTransaction ct = getCompositeTransactionManager().createCompositeTransaction(timeout);
for (ParticipantLink pl : transaction.getParticipantLinks()) {
ParticipantAdapterImp pa = new ParticipantAdapterImp(pl);
TccParticipant p = new TccParticipant(pa);
ct.addParticipant(p);
}
return ct;
}
private long deriveTimeout(Transaction transaction) {
long timeout = 10000L;
long now = System.currentTimeMillis();
for (ParticipantLink pl : transaction.getParticipantLinks()) {
long expires = toTimestamp( pl.getExpires());
long participantTimeout = expires - now;
if (participantTimeout > timeout) {
timeout = participantTimeout;
}
}
return timeout;
}
private long toTimestamp(String expires) {
Calendar cal = DatatypeConverter.parseDateTime(expires);
return cal.getTimeInMillis();
}
@Override
@PUT
@Path("/confirm")
public void confirm(Transaction transaction) {
validateRequestIntegrity(transaction);
CompositeTransaction taasTransaction = createTaasTransaction(transaction);
commitTransaction(taasTransaction);
}
private void validateRequestIntegrity(Transaction transaction) {
if (transaction == null) failWithInvalidRequest("transaction must not be null");
for (ParticipantLink pl : transaction.getParticipantLinks()) {
validateParticipantLink(pl);
}
}
private void validateParticipantLink(ParticipantLink pl) {
if (pl.getExpires() == null) failWithInvalidRequest("each participantLink must have an 'expires'");
try {
toTimestamp(pl.getExpires());
} catch (IllegalArgumentException e) {
failWithInvalidRequest("invalid date format for participantLink 'expires': "+pl.getExpires());
}
if (pl.getUri() == null) failWithInvalidRequest("each participantLink must have a value for 'uri'");
}
private void failWithInvalidRequest(String message) {
LOGGER.logWarning(message);
Response response = Response.status(400).entity(message).type(MediaType.TEXT_PLAIN).build();
throw new WebApplicationException(response);
}
private void commitTransaction(CompositeTransaction taasTransaction) {
try {
taasTransaction.commit();
} catch (SecurityException e) {
// no permission means timeout / cancel everywhere
throwCancelledException();
} catch (RollbackException e) {
throwCancelledException();
} catch (HeurRollbackException e) {
throwCancelledException();
} catch (Exception e) {
LOGGER.logError("Unexpected error during confirm", e);
Response response = Response.status(409).entity("partial confirmation - check each participant for details").type(MediaType.TEXT_PLAIN).build();
throw new WebApplicationException(response);
}
}
private void throwCancelledException() {
Response response = Response.status(404).entity("transaction has timed out and was cancelled").type(MediaType.TEXT_PLAIN).build();
throw new WebApplicationException(response);
}
@Override
@PUT
@Path("/cancel")
public void cancel(Transaction transaction) {
validateRequestIntegrity(transaction);
CompositeTransaction taasTransaction = createTaasTransaction(transaction);
rollbackTransaction(taasTransaction);
}
private void rollbackTransaction(CompositeTransaction taasTransaction) {
try {
taasTransaction.rollback();
} catch (Exception e) {
// ignore but log
LOGGER.logWarning("Unexpected error during rollback", e);
}
}
}