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

com.atomikos.remoting.twopc.AtomikosRestPort Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * Copyright (C) 2000-2020 Atomikos 
 *
 * LICENSE CONDITIONS
 *
 * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
 */

package com.atomikos.remoting.twopc;

import java.util.Map;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.Participant;
import com.atomikos.icatch.RollbackException;
import com.atomikos.icatch.SysException;
import com.atomikos.icatch.TransactionService;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.icatch.imp.CoordinatorImp;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.recovery.LogException;
import com.atomikos.recovery.LogReadException;
import com.atomikos.recovery.PendingTransactionRecord;
import com.atomikos.recovery.RecoveryLog;
import com.atomikos.recovery.TxState;
import com.atomikos.remoting.support.HeaderNames;

 /**
  * A REST port for making the local JVM's invocations part of the transaction scope
  * of the calling client (if any). An instance needs to be running if you want to 
  * accept incoming transactional REST calls.
  */

@Path("/atomikos")
@Consumes(HeaderNames.MimeType.APPLICATION_VND_ATOMIKOS_JSON)
public class AtomikosRestPort {
	
	public static final String REST_URL_PROPERTY_NAME = "com.atomikos.icatch.rest_port_url";

	private static final Logger LOGGER = LoggerFactory.createLogger(AtomikosRestPort.class);
	
	private static String atomikosRestPortUrl;
	
	/**
	 * Sets the URL for this port - either from jta.properties or as detected on first invocation of our interceptor.
	 * Needed because JAX-RS does not have an easy why of finding out on which URL we are bound.
	 * 
	 * @param url Can be null in which case this method does nothing. If the URL was already set then a second invocation will not have any effect.
	 */
	public static void setUrl(String url) {
		if (atomikosRestPortUrl == null && url != null) { //cf case 181280
			atomikosRestPortUrl = url;
			if (!atomikosRestPortUrl.endsWith("/")) 
				atomikosRestPortUrl = atomikosRestPortUrl + "/";
		} 
	}
	
	public static String getUrl() {
		return atomikosRestPortUrl;
	}
	
	public static String buildParticipantUrl(CompositeTransaction ct) throws SysException {
		assertRestPortUrlSet();
		return getUrl()+ct.getCompositeCoordinator().getRootId()+"/" + ct.getCompositeCoordinator().getCoordinatorId();
	}

	private static void assertRestPortUrlSet() {
		if (getUrl() == null) {
			throw new SysException("Please set property " + AtomikosRestPort.REST_URL_PROPERTY_NAME + " - see https://www.atomikos.com/Documentation/ConfiguringRemoting for details");
		}
	}

	private static RecoveryLog recoveryLog;
	
	public static void init(String url) {
	    recoveryLog = Configuration.getRecoveryLog();
	    setUrl(url);
	}
	
	private static String buildParticipantUrl(String root, String coordinatorId) {
		return atomikosRestPortUrl + root + "/" + coordinatorId;
	}
	
	@GET
	public String ping() {
		return "Hello from Atomikos!";
	}

	@GET
	@Path("{coordinatorId}")
	public String getOutcome(@PathParam("coordinatorId") String coordinatorId) {
	    TxState ret = TxState.TERMINATED;
	    PendingTransactionRecord record = null;
        try {
            record = recoveryLog.get(coordinatorId);
        } catch (LogReadException e) {
            LOGGER.logWarning("Unexpected log exception", e);
            throw409(e);
        }
	    if (record != null) {
	        ret = record.state;
	    }
	    return ret.toString();
	}
	
	@POST
	@Path("{rootId}/{coordinatorId}")
	public Response prepare(@PathParam("rootId") String rootId, @PathParam("coordinatorId") String coordinatorId, Map cascadeList) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.logDebug("prepare ( ... ) received for root " + rootId);
		}
		TransactionService service = Configuration.getTransactionService();
		String extentUri = buildParticipantUrl(rootId, coordinatorId);
		Integer count = cascadeList.get(extentUri);
		Participant part = service.getParticipant(rootId);
		if (part == null) {
			return Response.status(Status.NOT_FOUND).entity(rootId).build();
		}
		part.setGlobalSiblingCount(count);
		part.setCascadeList(cascadeList);
		int result = -1;
		try {
			result = part.prepare();
		} catch (RollbackException rb) {
			LOGGER.logWarning("Error in prepare for root " + rootId, rb);
			throw404();
		} catch (Exception e) {
			LOGGER.logWarning("Error in prepare for root " + rootId, e);
			throw409(e);
		}
		
		return Response.status(Status.CREATED).entity(result).build();
	}

	private void throw404() {
		Response response = Response.status(Status.NOT_FOUND)
				.entity("Transaction has timed out and was rolledback")
				.type(MediaType.TEXT_PLAIN).build();
		throw new WebApplicationException(response);
	}

	private void throw409(Exception e) {
		Response response = Response.status(Status.CONFLICT).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
		throw new WebApplicationException(response);
	}

	@PUT
	@Path("{rootId}/{coordinatorId}/{onePhase}")
	public Response commit(@PathParam("rootId") String rootId, @PathParam("coordinatorId") String coordinatorId, @PathParam("onePhase") boolean onePhase) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.logDebug("commit() received for root " + rootId + " onePhase = " + onePhase);
		}
		TransactionService service = Configuration.getTransactionService();
		CoordinatorImp part = (CoordinatorImp) service.getParticipant(rootId);
		
		if (part != null) { 
		    if (!part.getState().isFinalState()) { //ignore remote commit retries that worked here the first time
		        if (!part.getState().transitionAllowedTo(TxState.COMMITTING)) {
		            if(!onePhase) {
		                LOGGER.logWarning("Commit no longer allowed for root " + rootId + " - probably due to heuristic rollback?");
		                return Response.status(Status.CONFLICT).entity(rootId).build(); 
		            }
		        } else {
		            try {
		                part.commit(onePhase);
		            } catch (RollbackException rb) {
		                LOGGER.logWarning("Error in commit for root " + rootId, rb);
		                throw404();
		            } catch (Exception e) {
		                LOGGER.logWarning("Error in commit for root " + rootId, e);
		                throw409(e);
		            }   
		        }
		    }
		} else { //abandoned in OLTP? => delegate to log and recovery
	
                if (!onePhase) {
                    try {
                        delegateToRecovery(coordinatorId, true);
                    } catch (LogException e) {
                        LOGGER.logWarning("Error in commit for root " + rootId, e);
                        throw409(e);
                    }
                } else {
                    Response response = Response.status(Status.CONFLICT).entity(rootId).type(MediaType.TEXT_PLAIN).build();
                    throw new WebApplicationException(response);
                }

		}
		
 		
		return Response.status(Status.NO_CONTENT).build();
	}

	private void delegateToRecovery(String coordinatorId, boolean commit) throws LogException {
        if (recoveryLog == null) {
            recoveryLog = Configuration.getRecoveryLog();
        }
        if (commit) {
            recoveryLog.recordAsCommitting(coordinatorId);
        } else {
            recoveryLog.forget(coordinatorId);
        }
    }

    @DELETE
	@Path("{rootId}/{coordinatorId}")
	public Response rollback(@PathParam("rootId") String rootId, @PathParam("coordinatorId") String coordinatorId) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.logDebug("rollback() received for root " + rootId);
		}
		TransactionService service = Configuration.getTransactionService();
		Participant part = service.getParticipant(rootId);
		if (part != null) { //null for remote retry of rollback that worked here the first time
			try {
				part.rollback();
			} catch (Exception e) {
				LOGGER.logWarning("Error in rollback for root " + rootId, e);
				throw409(e);
			}
		} else { //abandoned in OLTP? => delegate to log and recovery
	            try {
                    delegateToRecovery(coordinatorId, false);
                } catch (LogException e) {
                    LOGGER.logWarning("Error in rollback for root " + rootId, e);
                    throw409(e);
                }
		}

		return Response.status(Status.NO_CONTENT).build();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy