/* * JBoss, Home of Professional Open Source * Copyright 2010, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * 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, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2010 * @author JBoss Inc. */ package org.jboss.jbossts.star.test; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import javax.net.ssl.SSLContext; 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.*; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.exceptions.ObjectStoreException; import com.arjuna.ats.arjuna.objectstore.RecoveryStore; import com.arjuna.ats.arjuna.objectstore.StoreManager; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; import com.arjuna.ats.internal.jta.transaction.arjunacore.AtomicAction; import com.squareup.okhttp.OkHttpClient; import io.undertow.Undertow; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; import org.jboss.jbossts.star.provider.HttpResponseException; import org.jboss.jbossts.star.provider.HttpResponseMapper; import org.jboss.jbossts.star.provider.NotFoundMapper; import org.jboss.jbossts.star.provider.TMUnavailableMapper; import org.jboss.jbossts.star.provider.TransactionStatusMapper; import org.jboss.jbossts.star.service.Coordinator; import org.jboss.jbossts.star.service.TMApplication; import org.jboss.jbossts.star.util.*; import org.jboss.logging.Logger; import org.jboss.resteasy.plugins.server.tjws.TJWSEmbeddedJaxrsServer; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.spi.Registry; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import com.sun.grizzly.http.SelectorThread; //import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory; public class BaseTest { protected final static Logger log = Logger.getLogger(BaseTest.class); protected static final ExecutorService executor = Executors.newFixedThreadPool(4); protected static boolean USE_RESTEASY = false; protected static boolean USE_UNDERTOW = true; private static HttpServer grizzlyServer; protected static final String USE_SPDY_PROP = "rts.usespdy"; protected static final String USE_SSL_PROP = "rts.usessl"; protected static final boolean USE_SPDY = Boolean.getBoolean(USE_SPDY_PROP); protected static final boolean USE_SSL = Boolean.getBoolean(USE_SSL_PROP) || USE_SPDY; protected static String SCHEME = USE_SSL ? "https" : "http"; protected static final int PORT = 58081; protected static String SURL = SCHEME + "://localhost:" + PORT + '/'; protected static final String PSEGMENT = "txresource"; protected static final String NO_RESPONSE_SEGMENT = "no-response"; protected static final String PURL = SURL + PSEGMENT; protected static final String PURL_NO_RESPONSE = PURL + "/" + NO_RESPONSE_SEGMENT; protected static String TXN_MGR_URL = SURL + "tx/transaction-manager"; private static TJWSEmbeddedJaxrsServer server = null; private static SelectorThread threadSelector = null; private static UndertowJaxrsServer undertow; protected static void setTxnMgrUrl(String txnMgrUrl) { TXN_MGR_URL = txnMgrUrl; } protected static void startUndertow(Class<?> ... classes) throws Exception { undertow = new UndertowJaxrsServer(); undertow.start(Undertow.builder().addHttpListener(PORT, "localhost")); undertow.deploy(new TMApplication(classes));//, SURL + "tx/"); System.out.printf("server is ready:"); } protected static void startRestEasy(Class<?> ... classes) throws Exception { server = new TJWSEmbeddedJaxrsServer(); server.setPort(PORT); server.start(); Registry registry = server.getDeployment().getRegistry(); ResteasyProviderFactory factory = server.getDeployment().getDispatcher().getProviderFactory(); if (classes != null) for (Class<?> clazz : classes) registry.addPerRequestResource(clazz); factory.registerProvider(TMUnavailableMapper.class); factory.registerProvider(TransactionStatusMapper.class); factory.registerProvider(HttpResponseMapper.class); factory.registerProvider(NotFoundMapper.class); } public static Future<String> submitJob(Callable<String> job) { return executor.submit(job); } public static void submitJob(Runnable job) { executor.submit(job); } protected static void startJersey(String packages) throws Exception { final Map<String, String> initParams = new HashMap<String, String>(); initParams.put("com.sun.jersey.config.property.packages", packages); initParams.put(ServerProperties.PROVIDER_PACKAGES, packages); // initParams.put(ServerProperties.PROVIDER_PACKAGES, Coordinator.class.getPackage().getName()); try { if (USE_SSL) { URI baseUri= UriBuilder.fromUri(SURL).build(); String trustStoreFile = System.getProperty("javax.net.ssl.trustStore"); String trustStorePswd = System.getProperty("javax.net.ssl.trustStorePassword"); if (trustStoreFile == null || trustStorePswd == null) throw new IllegalArgumentException("Please set SSL javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword to use SPDY suppport"); grizzlyServer = SpdyEnabledHttpServer.create(baseUri, initParams, trustStoreFile, trustStorePswd, 50, USE_SPDY); } else { URI baseUri= UriBuilder.fromUri(SURL).build(); // threadSelector = GrizzlyWebContainerFactory.create(baseUri, initParams); final ResourceConfig resourceConfig = new ResourceConfig();//Coordinator.class); resourceConfig.packages("org.jboss.jbossts.star.service", "org.jboss.jbossts.star.provider", "org.jboss.jbossts.star.test"); grizzlyServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, resourceConfig); } } catch (IOException e) { log.infof(e, "Error starting Grizzly"); } } public static void startContainer(String txnMgrUrl, String packages, Class<?> ... classes) throws Exception { TxSupport.setTxnMgrUrl(txnMgrUrl); if (USE_SPDY) TxSupport.setHttpConnectionCreator(new SpdyConnection()); if (USE_RESTEASY) startRestEasy(classes); else if (USE_UNDERTOW) startUndertow(TransactionalResource.class); else startJersey(packages); } private static class SpdyConnection implements HttpConnectionCreator { private OkHttpClient spdyClient; SpdyConnection() { spdyClient = new OkHttpClient(); try { //sslContext = getSSLContext(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, null, null); spdyClient.setSslSocketFactory(sslContext.getSocketFactory()); } catch (Exception e) { throw new AssertionError(); // The system has no TLS. Just give up. } } @Override public HttpURLConnection open(URL url) throws IOException { return spdyClient.open(url); } } public static void startContainer(String txnMgrUrl) throws Exception { startContainer(txnMgrUrl, "org.jboss.jbossts.star.service;org.jboss.jbossts.star.provider;org.jboss.jbossts.star.test", TransactionalResource.class, Coordinator.class); } private static void clearObjectStore(String type) { try { RecoveryStore recoveryStore = StoreManager.getRecoveryStore(); InputObjectState states = new InputObjectState(); if (recoveryStore.allObjUids(type, states) && states.notempty()) { boolean finished = false; do { Uid uid = UidHelper.unpackFrom(states); if (uid.notEquals(Uid.nullUid())) { recoveryStore.remove_committed(uid, type); } else { finished = true; } } while (!finished); } } catch (Exception e) { e.printStackTrace(); } } protected boolean writeObjectStoreRecord(OSRecordHolder holder) { try { return StoreManager.getRecoveryStore().write_committed(holder.uid, holder.type, holder.oos); } catch (ObjectStoreException e) { return false; } } protected static OSRecordHolder readObjectStoreRecord(String type) { try { RecoveryStore recoveryStore = StoreManager.getRecoveryStore(); InputObjectState states = new InputObjectState(); if (recoveryStore.allObjUids(type, states) && states.notempty()) { Uid uid = UidHelper.unpackFrom(states); if (uid.notEquals(Uid.nullUid())) { InputObjectState ios = recoveryStore.read_committed(uid, type); return new OSRecordHolder(uid, type, ios); } } } catch (Exception e) { e.printStackTrace(); } return null; } @AfterClass public static void afterClass() throws Exception { if (undertow !=null) { undertow.stop(); undertow = null; } if (server != null) { server.stop(); server = null; } if (threadSelector != null) { threadSelector.stopEndpoint(); threadSelector = null; } else if (grizzlyServer != null) { grizzlyServer.shutdownNow(); } } @Before public void before() throws Exception { TransactionalResource.faults.clear(); clearObjectStore(new AtomicAction().type()); } @Test public void nullTest() throws Exception { // need at least one test } protected String enlistResource(TxSupport txn, String pUrl) { return txn.enlistTestResource(pUrl, false); } private StringBuilder getResourceUpdateUrl(String pUrl, String pid, String name, String value) { StringBuilder sb = new StringBuilder(pUrl); if (pid != null) sb.append("?pId=").append(pid).append("&name="); else sb.append("?name="); sb.append(name); if (value != null) sb.append("&value=").append(value); return sb; } /** * Modify a transactional participant * @param txn the transaction * @param pUrl the transactional participant * @param pid an id * @param name name of a property to update * @param value the new value of the property * @return the response body */ protected String modifyResource(TxSupport txn, String pUrl, String pid, String name, String value) { // tell the resource to modify some data and pass the transaction enlistment url along with the request return txn.httpRequest(new int[] {HttpURLConnection.HTTP_OK}, getResourceUpdateUrl(pUrl, pid, name, value).toString(), "GET", TxMediaType.PLAIN_MEDIA_TYPE); } protected String getResourceProperty(TxSupport txn, String pUrl, String pid, String name) { return txn.httpRequest(new int[] {HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_NO_CONTENT}, getResourceUpdateUrl(pUrl, pid, name, null).toString(), "GET", TxMediaType.PLAIN_MEDIA_TYPE); } private static class Work { String id; String tid; String uri; String pLinks; String enlistUrl; String recoveryUrl; String fault; Map<String, String> oldState; Map<String, String> newState; String status; int vStatus = 0; int syncCount = 0; int commitCnt = 0; int prepareCnt = 0; int rollbackCnt = 0; int commmitOnePhaseCnt = 0; Work(String id, String tid, String uri, String pLinks, String enlistUrl, String recoveryUrl, String fault) { this.id = id; this.tid = tid; this.uri = uri; this.pLinks = pLinks; this.enlistUrl = enlistUrl; this.recoveryUrl = recoveryUrl; this.fault = fault; this.oldState = new HashMap<String, String> (); this.newState = new HashMap<String, String> (); } public void start() { newState.clear(); newState.putAll(oldState); } public void end(boolean commit) { if (commit) { oldState.clear(); oldState.putAll(newState); } } public boolean inTxn() { return status != null && TxStatus.fromStatus(status).isActive(); // return TxSupport.isActive(status); } } @Path(PSEGMENT) public static class TransactionalResource { private static int pid = 0; static Map<String, Work> faults = new HashMap<String, Work> (); 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); } private String moveParticipant(Work work, String nid, String register, boolean twoPhaseAware, boolean isVolatile) { TxSupport txn = new TxSupport(); faults.remove(work.id); work = makeWork(txn, PURL, nid, work.tid, work.enlistUrl, twoPhaseAware, isVolatile, work.recoveryUrl, work.fault); faults.put(nid, work); // if register is true then tell the transaction manager about the new location - otherwise the old // URIs will be used for transaction termination. This is used to test that the coordinator uses // the recovery URI correctly if ("true".equals(register)) { Map<String, String> reqHeaders = new HashMap<String, String> (); reqHeaders.put("Link", work.pLinks); txn.httpRequest(new int[] {HttpURLConnection.HTTP_OK}, work.recoveryUrl, "PUT", TxMediaType.POST_MEDIA_TYPE, null, null, reqHeaders); } return nid; } @SuppressWarnings({"UnusedDeclaration"}) @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(), TxSupport.extractUri(info), 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)) res = moveParticipant(work, arg, register, isTwoPhaseAware, isVolatile); 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 String path = TxSupport.extractUri(info); if (work == null) { int id = ++pid; work = makeWork(txn, path, String.valueOf(id), txId, enlistUrl, isTwoPhaseAware, isVolatileParticipant, null, fault); } else { Work newWork = makeWork(txn, path, 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(path).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; } @POST @Produces(TxMediaType.PLAIN_MEDIA_TYPE) @Path(NO_RESPONSE_SEGMENT) public String enlistNoResponseParticipant(@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 { return enlist(info, pId, fault, twoPhaseAware, isVolatile, enlistUrl); } @SuppressWarnings({"UnusedDeclaration"}) @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); } @SuppressWarnings({"UnusedDeclaration"}) @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(); } @SuppressWarnings({"UnusedDeclaration"}) @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 ("HALT".equals(fault)) Runtime.getRuntime().halt(1); else 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 ("CRUNTIME".equals(fault)) { work.status = TxStatus.TransactionStatusUnknown.name(); throw new RuntimeException("Test runtime exception"); } 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(NO_RESPONSE_SEGMENT + "/{pId}/{tId}/terminator") public Response terminateWithoutResponse(@PathParam("pId") @DefaultValue("")String pId, @PathParam("tId") @DefaultValue("")String tId, String content) { Response response = terminate(pId, tId, content); return Response.status(response.getStatus()).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); } @SuppressWarnings({"UnusedDeclaration"}) @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(); } @SuppressWarnings({"UnusedDeclaration"}) @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); } @SuppressWarnings({"UnusedDeclaration"}) @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); } } }