/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.wildfly.test.extension.rts.common; import java.io.IOException; import java.net.HttpURLConnection; import java.util.HashMap; import java.util.Map; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.jboss.jbossts.star.provider.HttpResponseException; 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.TxStatusMediaType; import org.jboss.jbossts.star.util.TxSupport; @Path(WorkRestATResource.PATH_SEGMENT) public final class WorkRestATResource { public static final String PATH_SEGMENT = "txresource"; private static int pid = 0; private static Map<String, Work> faults = new HashMap<String, Work>(); public static void clearFaults() { faults.clear(); } @GET public String getBasic(@Context UriInfo info, @QueryParam("pId") @DefaultValue("") String pId, @QueryParam("context") @DefaultValue("") String ctx, @QueryParam("name") @DefaultValue("") String name, @QueryParam("value") @DefaultValue("") String value, @QueryParam("query") @DefaultValue("pUrl") String query, @QueryParam("arg") @DefaultValue("") String arg, @QueryParam("twoPhaseAware") @DefaultValue("true") String twoPhaseAware, @QueryParam("isVolatile") @DefaultValue("false") String isVolatileParticipant, @QueryParam("register") @DefaultValue("true") String register) { Work work = faults.get(pId); String res = null; boolean isVolatile = "true".equals(isVolatileParticipant); boolean isTwoPhaseAware = "true".equals(twoPhaseAware); if (name.length() != 0) { if (value.length() != 0) { if (work == null) { work = makeWork(new TxSupport(), info.getAbsolutePath().toString(), String.valueOf(++pid), null, null, isTwoPhaseAware, isVolatile, null, null); work.oldState.put(name, value); faults.put(work.id, work); return work.id; } work.newState.put(name, value); } if (work != null) { if ("syncCount".equals(name)) res = String.valueOf(work.syncCount); else if ("commitCnt".equals(name)) res = String.valueOf(work.commitCnt); else if ("prepareCnt".equals(name)) res = String.valueOf(work.prepareCnt); else if ("rollbackCnt".equals(name)) res = String.valueOf(work.rollbackCnt); else if ("commmitOnePhaseCnt".equals(name)) res = String.valueOf(work.commmitOnePhaseCnt); else if (work.inTxn()) res = work.newState.get(name); else res = work.oldState.get(name); } } if (work == null) throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); if ("move".equals(query)) { /* Ignore*/ }else if ("recoveryUrl".equals(query)) res = work.recoveryUrl; else if ("status".equals(query)) res = work.status; else if (res == null) res = work.pLinks; return res; // null will generate a 204 status code (no content) } @POST @Produces(TxMediaType.PLAIN_MEDIA_TYPE) public String enlist(@Context UriInfo info, @QueryParam("pId") @DefaultValue("") String pId, @QueryParam("fault") @DefaultValue("") String fault, @QueryParam("twoPhaseAware") @DefaultValue("true") String twoPhaseAware, @QueryParam("isVolatile") @DefaultValue("false") String isVolatile, String enlistUrl) throws IOException { Work work = faults.get(pId); TxSupport txn = new TxSupport(); String txId = enlistUrl.substring(enlistUrl.lastIndexOf('/') + 1); boolean isTwoPhaseAware = "true".equals(twoPhaseAware); boolean isVolatileParticipant = "true".equals(isVolatile); String vRegistration = null; // URI for registering with the volatile phase String vParticipantLink = null; // URI for handling pre and post 2PC phases if (work == null) { int id = ++pid; work = makeWork(txn, info.getAbsolutePath().toString(), String.valueOf(id), txId, enlistUrl, isTwoPhaseAware, isVolatileParticipant, null, fault); } else { Work newWork = makeWork(txn, info.getAbsolutePath().toString(), work.id, txId, enlistUrl, isTwoPhaseAware, isVolatileParticipant, null, fault); newWork.oldState = work.oldState; newWork.newState = work.newState; work = newWork; } if (enlistUrl.indexOf(',') != -1) { String[] urls = enlistUrl.split(","); if (urls.length < 2) throw new WebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST); enlistUrl = urls[0]; vRegistration = urls[1]; String vParticipant = new StringBuilder(info.getAbsolutePath().toString()).append('/').append(work.id).append('/') .append(txId).append('/').append("vp").toString(); vParticipantLink = txn.addLink2(new StringBuilder(), TxLinkNames.VOLATILE_PARTICIPANT, vParticipant, true) .toString(); } try { // enlist TestResource in the transaction as a participant work.recoveryUrl = txn.enlistParticipant(enlistUrl, work.pLinks); if (vParticipantLink != null) txn.enlistVolatileParticipant(vRegistration, vParticipantLink); } catch (HttpResponseException e) { throw new WebApplicationException(e.getActualResponse()); } work.status = TxStatus.TransactionActive.name(); work.start(); faults.put(work.id, work); return work.id; } @PUT @Path("{pId}/{tId}/vp") public Response directSynchronizations(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { return synchronizations(pId, tId, content); } @PUT @Path("{pId}/{tId}/volatile-participant") public Response synchronizations(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { Work work = faults.get(pId); TxStatus txStatus; int vStatus; if (work == null) return Response.ok().build(); txStatus = content != null ? TxStatus.fromStatus(content) : TxStatus.TransactionStatusUnknown; vStatus = txStatus.equals(TxStatus.TransactionStatusUnknown) ? 1 : 2; if (vStatus == 2 && work.vStatus == 0) { // afterCompletion but coordinator never called beforeCompletion return Response.status(HttpURLConnection.HTTP_BAD_REQUEST).build(); } work.vStatus = vStatus; work.syncCount += 1; if (vStatus == 1 && "V_PREPARE".equals(work.fault)) return Response.status(HttpURLConnection.HTTP_CONFLICT).build(); else if (vStatus == 2 && "V_COMMIT".equals(work.fault)) return Response.status(HttpURLConnection.HTTP_CONFLICT).build(); return Response.ok().build(); } @PUT @Path("{pId}/{tId}/terminator") public Response terminate(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { TxStatus status = TxSupport.toTxStatus(content); // String status = TxSupport.getStatus(content); Work work = faults.get(pId); if (work == null) return Response.status(HttpURLConnection.HTTP_NOT_FOUND).build(); String fault = work.fault; if (status.isPrepare()) { if ("READONLY".equals(fault)) { // faults.remove(pId); work.status = TxStatus.TransactionReadOnly.name(); } else if ("PREPARE_FAIL".equals(fault)) { // faults.remove(pId); return Response.status(HttpURLConnection.HTTP_CONFLICT).build(); // throw new WebApplicationException(HttpURLConnection.HTTP_CONFLICT); } else { if ("PDELAY".equals(fault)) { try { Thread.sleep(2000); } catch (InterruptedException e) { } } work.status = TxStatus.TransactionPrepared.name(); } } else if (status.isCommit() || status.isCommitOnePhase()) { if ("H_HAZARD".equals(fault)) work.status = TxStatus.TransactionHeuristicHazard.name(); else if ("H_ROLLBACK".equals(fault)) work.status = TxStatus.TransactionHeuristicRollback.name(); else if ("H_MIXED".equals(fault)) work.status = TxStatus.TransactionHeuristicMixed.name(); else { if ("CDELAY".equals(fault)) { try { Thread.sleep(3000); } catch (InterruptedException e) { // ok } } work.status = status.isCommitOnePhase() ? TxStatus.TransactionCommittedOnePhase.name() : TxStatus.TransactionCommitted.name(); work.end(true); } } else if (status.isAbort()) { if ("H_HAZARD".equals(fault)) work.status = TxStatus.TransactionHeuristicHazard.name(); else if ("H_COMMIT".equals(fault)) work.status = TxStatus.TransactionHeuristicCommit.name(); else if ("H_MIXED".equals(fault)) work.status = TxStatus.TransactionHeuristicMixed.name(); else { if ("ADELAY".equals(fault)) { try { Thread.sleep(2000); } catch (InterruptedException e) { // ok } } work.status = TxStatus.TransactionRolledBack.name(); work.end(false); // faults.remove(pId); } } else { return Response.status(HttpURLConnection.HTTP_BAD_REQUEST).build(); // throw new WebApplicationException(HttpURLConnection.HTTP_BAD_REQUEST); } // return TxSupport.toStatusContent(work.status); return Response.ok(TxSupport.toStatusContent(work.status)).build(); } @PUT @Path("{pId}/{tId}/prepare") public Response prepare(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { Work work = faults.get(pId); if (work != null) work.prepareCnt += 1; return terminate(pId, tId, TxStatusMediaType.TX_PREPARED); } @PUT @Path("{pId}/{tId}/commit") public Response commit(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { Work work = faults.get(pId); if (work != null) work.commitCnt += 1; return terminate(pId, tId, TxStatusMediaType.TX_COMMITTED); } @PUT @Path("{pId}/{tId}/rollback") public Response rollback(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { Work work = faults.get(pId); if (work != null) work.rollbackCnt += 1; return terminate(pId, tId, TxStatusMediaType.TX_ROLLEDBACK); } @PUT @Path("{pId}/{tId}/commit-one-phase") public Response commmitOnePhase(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId, String content) { Work work = faults.get(pId); if (work != null) work.commmitOnePhaseCnt += 1; return terminate(pId, tId, TxStatusMediaType.TX_COMMITTED_ONE_PHASE); } @HEAD @Path("{pId}/{tId}/participant") public Response getTerminator(@Context UriInfo info, @PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId) { Work work = faults.get(pId); if (work == null) return Response.status(HttpURLConnection.HTTP_BAD_REQUEST).build(); Response.ResponseBuilder builder = Response.ok(); builder.header("Link", work.pLinks); return builder.build(); } @GET @Path("{pId}/{tId}/participant") public String getStatus(@PathParam("pId") @DefaultValue("") String pId, @PathParam("tId") @DefaultValue("") String tId) { Work work = faults.get(pId); if (work == null) throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); return TxSupport.toStatusContent(work.status); } @DELETE @Path("{pId}/{tId}/participant") public void forgetWork(@PathParam("pId") String pId, @PathParam("tId") String tId) { Work work = faults.get(pId); if (work == null) throw new WebApplicationException(HttpURLConnection.HTTP_NOT_FOUND); faults.remove(pId); } public Work makeWork(TxSupport txn, String baseURI, String id, String txId, String enlistUrl, boolean twoPhaseAware, boolean isVolatile, String recoveryUrl, String fault) { String linkHeader = twoPhaseAware ? txn.makeTwoPhaseAwareParticipantLinkHeader(baseURI, isVolatile, id, txId) : txn .makeTwoPhaseUnAwareParticipantLinkHeader(baseURI, isVolatile, id, txId, true); return new Work(id, txId, baseURI + '/' + id, linkHeader, enlistUrl, recoveryUrl, fault); } }