All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atomikos.icatch.tcc.rest.CoordinatorImp Maven / Gradle / Ivy

There is a newer version: 4.0.6
Show newest version
/**
 * Copyright (C) 2000-2016 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) {	
		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);
		}
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy