package org.jboss.narayana.rest.integration; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.jboss.jbossts.star.util.TxLinkNames; import org.jboss.jbossts.star.util.TxMediaType; import org.jboss.jbossts.star.util.TxStatus; import org.jboss.jbossts.star.util.TxSupport; import org.jboss.logging.Logger; import org.jboss.narayana.rest.integration.api.Aborted; import org.jboss.narayana.rest.integration.api.HeuristicException; import org.jboss.narayana.rest.integration.api.HeuristicType; import org.jboss.narayana.rest.integration.api.ParticipantException; import org.jboss.narayana.rest.integration.api.Prepared; import org.jboss.narayana.rest.integration.api.ReadOnly; import org.jboss.narayana.rest.integration.api.Vote; /** * * @author <a href="mailto:gytis@redhat.com">Gytis Trikleris</a> * */ @Path(ParticipantResource.BASE_PATH_SEGMENT + "/{participantId}") public final class ParticipantResource { public static final String BASE_PATH_SEGMENT = "rest-at-participant"; private static final Logger LOG = Logger.getLogger(ParticipantResource.class); @HEAD public Response getTerminatorUrl(@PathParam("participantId") final String participantId, @Context final UriInfo uriInfo) { if (LOG.isTraceEnabled()) { LOG.trace("HEAD request on ParticipantResource. ParticipantId: " + participantId); } if (ParticipantsContainer.getInstance().getParticipantInformation(participantId) == null) { if (LOG.isTraceEnabled()) { LOG.trace("Participant with id " + participantId + " was not found."); } return Response.status(404).build(); } Response.ResponseBuilder builder = Response.ok(); TxSupport.addLinkHeader(builder, uriInfo, TxLinkNames.TERMINATOR, TxLinkNames.TERMINATOR); return builder.build(); } @GET @Produces(TxMediaType.TX_STATUS_MEDIA_TYPE) public Response getStatus(@PathParam("participantId") final String participantId) { if (LOG.isTraceEnabled()) { LOG.trace("GET request on ParticipantResource. ParticipantId: " + participantId); } final ParticipantInformation participantInformation = ParticipantsContainer.getInstance() .getParticipantInformation(participantId); if (participantInformation == null) { if (LOG.isTraceEnabled()) { LOG.trace("Participant with id " + participantId + " was not found."); } return Response.status(404).build(); } return Response.ok(TxSupport.toStatusContent(participantInformation.getStatus())).build(); } @PUT @Consumes(TxMediaType.TX_STATUS_MEDIA_TYPE) @Produces(TxMediaType.TX_STATUS_MEDIA_TYPE) public Response terminate(@PathParam("participantId") final String participantId, final String content) throws HeuristicException { if (LOG.isTraceEnabled()) { LOG.trace("PUT request on ParticipantResource. ParticipantId: " + participantId + ", content: " + content); } final ParticipantInformation participantInformation = ParticipantsContainer.getInstance() .getParticipantInformation(participantId); if (participantInformation == null) { if (LOG.isTraceEnabled()) { LOG.trace("Participant with id " + participantId + " was not found."); } return Response.status(404).build(); } String status = TxSupport.getStatus(content); if (TxStatus.isPrepare(status)) { if (!canPrepare(participantInformation)) { return Response.status(412).build(); } Vote vote = prepare(participantInformation); return voteToResponse(vote); } else if (TxStatus.isCommit(status)) { if (!canCommit(participantInformation)) { return Response.status(412).build(); } commit(participantInformation); return Response.ok().entity(TxSupport.toStatusContent(TxStatus.TransactionCommitted.name())).build(); } else if (TxStatus.isCommitOnePhase(status)) { if (!canCommitOnePhase(participantInformation)) { return Response.status(412).build(); } commitOnePhase(participantInformation); return Response.ok().entity(TxSupport.toStatusContent(TxStatus.TransactionCommittedOnePhase.name())).build(); } else if (TxStatus.isAbort(status)) { rollback(participantInformation); return Response.ok().entity(TxSupport.toStatusContent(TxStatus.TransactionRolledBack.name())).build(); } return Response.status(400).build(); } @DELETE public Response forgetHeuristic(@PathParam("participantId") final String participantId) { if (LOG.isTraceEnabled()) { LOG.trace("DELETE request on ParticipantResource. ParticipantId: " + participantId); } final ParticipantInformation participantInformation = ParticipantsContainer.getInstance() .getParticipantInformation(participantId); if (participantInformation == null) { if (LOG.isTraceEnabled()) { LOG.trace("Participant with id " + participantId + " was not found."); } return Response.status(404).build(); } if (TxStatus.TransactionHeuristicCommit.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicRollback.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicHazard.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicMixed.name().equals(participantInformation.getStatus())) { RecoveryManager.getInstance().removeParticipantInformation(participantInformation); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); return Response.ok().build(); } return Response.status(412).build(); } private Vote prepare(final ParticipantInformation participantInformation) throws HeuristicException { if (isHeuristic(participantInformation)) { return prepareHeuristic(participantInformation); } participantInformation.setStatus(TxStatus.TransactionPreparing.name()); final Vote vote; try { vote = participantInformation.getParticipant().prepare(); } catch (ParticipantException e) { participantInformation.setStatus(TxStatus.TransactionActive.name()); throw e; } if (vote instanceof Aborted) { rollback(participantInformation); } else if (vote instanceof Prepared) { participantInformation.setStatus(TxStatus.TransactionPrepared.name()); RecoveryManager.getInstance().persistParticipantInformation(participantInformation); } else if (vote instanceof ReadOnly) { readOnly(participantInformation); } return vote; } private Vote prepareHeuristic(final ParticipantInformation participantInformation) { if (TxStatus.TransactionHeuristicCommit.name().equals(participantInformation.getStatus())) { return new Prepared(); } else { RecoveryManager.getInstance().removeParticipantInformation(participantInformation); return new Aborted(); } } private void commit(final ParticipantInformation participantInformation) throws HeuristicException { if (isHeuristic(participantInformation)) { commitHeuristic(participantInformation); } else { participantInformation.setStatus(TxStatus.TransactionCommitting.name()); try { participantInformation.getParticipant().commit(); } catch (HeuristicException e) { if (!e.getHeuristicType().equals(HeuristicType.HEURISTIC_COMMIT)) { participantInformation.setStatus(e.getHeuristicType().toTxStatus()); RecoveryManager.getInstance().persistParticipantInformation(participantInformation); throw new HeuristicException(e.getHeuristicType()); } } catch (ParticipantException e) { participantInformation.setStatus(TxStatus.TransactionPrepared.name()); throw e; } participantInformation.setStatus(TxStatus.TransactionCommitted.name()); RecoveryManager.getInstance().removeParticipantInformation(participantInformation); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } } private void commitHeuristic(final ParticipantInformation participantInformation) throws HeuristicException { if (!TxStatus.TransactionHeuristicCommit.name().equals(participantInformation.getStatus())) { throw new HeuristicException(HeuristicType.fromTxStatus(participantInformation.getStatus())); } else { participantInformation.setStatus(TxStatus.TransactionCommitted.name()); RecoveryManager.getInstance().removeParticipantInformation(participantInformation); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } } private void commitOnePhase(final ParticipantInformation participantInformation) { if (!isHeuristic(participantInformation)) { participantInformation.setStatus(TxStatus.TransactionCommitting.name()); try { participantInformation.getParticipant().commitOnePhase(); } catch (ParticipantException e) { participantInformation.setStatus(TxStatus.TransactionActive.name()); throw e; } participantInformation.setStatus(TxStatus.TransactionCommittedOnePhase.name()); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } } private void rollback(final ParticipantInformation participantInformation) throws HeuristicException { if (isHeuristic(participantInformation)) { rollbackHeuristic(participantInformation); } else { final String previousStatus = participantInformation.getStatus(); participantInformation.setStatus(TxStatus.TransactionRollingBack.name()); try { participantInformation.getParticipant().rollback(); } catch (HeuristicException e) { if (!e.getHeuristicType().equals(HeuristicType.HEURISTIC_ROLLBACK)) { participantInformation.setStatus(e.getHeuristicType().toTxStatus()); RecoveryManager.getInstance().persistParticipantInformation(participantInformation); throw new HeuristicException(e.getHeuristicType()); } } catch (ParticipantException e) { participantInformation.setStatus(previousStatus); throw e; } participantInformation.setStatus(TxStatus.TransactionRolledBack.name()); RecoveryManager.getInstance().removeParticipantInformation(participantInformation); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } } private void rollbackHeuristic(final ParticipantInformation participantInformation) throws HeuristicException { if (!TxStatus.TransactionHeuristicRollback.name().equals(participantInformation.getStatus())) { throw new HeuristicException(HeuristicType.fromTxStatus(participantInformation.getStatus())); } else { participantInformation.setStatus(TxStatus.TransactionRolledBack.name()); RecoveryManager.getInstance().removeParticipantInformation(participantInformation); ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } } private void readOnly(final ParticipantInformation participantInformation) { participantInformation.setStatus(TxStatus.TransactionReadOnly.name()); try { ClientBuilder.newClient().target(participantInformation.getRecoveryURL()).request().delete(); } catch (Exception e) { LOG.warn(e.getMessage(), e); } ParticipantsContainer.getInstance().removeParticipantInformation(participantInformation.getId()); } private Response voteToResponse(final Vote vote) { Response response; if (vote instanceof Prepared) { response = Response.ok().entity(TxSupport.toStatusContent(TxStatus.TransactionPrepared.name())).build(); } else if (vote instanceof ReadOnly) { response = Response.ok().entity(TxSupport.toStatusContent(TxStatus.TransactionReadOnly.name())).build(); } else { response = Response.status(409).entity(TxSupport.toStatusContent(TxStatus.TransactionRolledBack.name())).build(); } return response; } private boolean canPrepare(final ParticipantInformation participantInformation) { return TxStatus.TransactionActive.name().equals(participantInformation.getStatus()) || isHeuristic(participantInformation); } private boolean canCommit(final ParticipantInformation participantInformation) { return TxStatus.TransactionPrepared.name().equals(participantInformation.getStatus()) || isHeuristic(participantInformation); } private boolean canCommitOnePhase(final ParticipantInformation participantInformation) { return TxStatus.TransactionActive.name().equals(participantInformation.getStatus()) || isHeuristic(participantInformation); } private boolean isHeuristic(final ParticipantInformation participantInformation) { return TxStatus.TransactionHeuristicCommit.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicRollback.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicMixed.name().equals(participantInformation.getStatus()) || TxStatus.TransactionHeuristicHazard.name().equals(participantInformation.getStatus()); } }