/**
* Copyright 2003-2010 Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.ehcache.transaction.xa.processor;
import net.sf.ehcache.transaction.xa.EhcacheXAException;
import net.sf.ehcache.transaction.xa.EhcacheXAResourceImpl;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
/**
* Default implementation for XARequestProcessor.
*
* This class ties an Xid to an Executor service. This is necessary so that
* locking for 2pc by the same thread.
*
* {@link XARequestProcessor xaRequestProcessor}.
*
* @author Nabib El-Rahman
*/
public class XARequestProcessor {
private static volatile XAThreadPool xaProcessorPool;
private final ConcurrentMap<Xid, XAThreadPool.MultiRunner> executorMap =
new ConcurrentHashMap<Xid, XAThreadPool.MultiRunner>();
private EhcacheXAResourceImpl resourceImpl;
/**
* Constructor
*
* @param resourceImpl The EhcacheXAResourceImpl instance this processor will perform against
*/
public XARequestProcessor(EhcacheXAResourceImpl resourceImpl) {
this.resourceImpl = resourceImpl;
if (xaProcessorPool == null) {
xaProcessorPool = new XAThreadPool();
}
}
/**
* Release resources shared by all XARequestProcessors
*/
public static void shutdown() {
if (xaProcessorPool != null) {
xaProcessorPool.shutdown();
xaProcessorPool = null;
}
}
/**
* Process a XARequest
* @param request the XARequest
* @return the XAResource response code
* @throws XAException the XAException thrown by the XAResource
*/
public int process(XARequest request) throws XAException {
XAThreadPool.MultiRunner multiRunner = getOrCreateThread(request.getXid());
XAResponse xaResponse;
try {
xaResponse = (XAResponse) multiRunner.execute(new XARequestCallable(resourceImpl, request, request.getXid()));
} catch (InterruptedException e) {
cleanupThread(request.getXid());
throw new EhcacheXAException(e.getMessage(), XAException.XAER_RMERR, e);
} catch (ExecutionException e) {
cleanupThread(request.getXid());
throw new EhcacheXAException(e.getMessage(), XAException.XAER_RMERR, e);
}
if (xaResponse.getXaException() != null) {
cleanupThread(request.getXid());
throw new EhcacheXAException("XA " + request.getRequestType().toString().toLowerCase() +
" request failed on [" + request.getXid() + "]", xaResponse.getXaException().errorCode,
xaResponse.getXaException());
}
if (request.getRequestType().equals(XARequest.RequestType.COMMIT) ||
request.getRequestType().equals(XARequest.RequestType.ROLLBACK) ||
request.getRequestType().equals(XARequest.RequestType.FORGET) ||
(request.getRequestType().equals(XARequest.RequestType.PREPARE) && xaResponse.getFlags() == XAResource.XA_RDONLY)) {
cleanupThread(request.getXid());
}
return xaResponse.getFlags();
}
/**
* Gets the executor service for a Transaction, either by creating a new one if none exists, or returning the
* existing one
* @param xid The Xid of the Transaction
* @return the ExecutorService for that Transaction
*/
private XAThreadPool.MultiRunner getOrCreateThread(Xid xid) {
XAThreadPool.MultiRunner service = executorMap.get(xid);
if (service == null) {
service = xaProcessorPool.getMultiRunner();
executorMap.put(xid, service);
}
return service;
}
/**
* Removes the ExecutorService from the map and shuts it down
* @param xid The Xid of the Transaction
*/
private void cleanupThread(Xid xid) {
XAThreadPool.MultiRunner service = executorMap.remove(xid);
service.release();
}
/**
* Class to furnish
* @author Nabib El-Rahman
*
*/
private static class XARequestCallable implements Callable<XAResponse> {
private final EhcacheXAResourceImpl resourceImpl;
private final XARequest request;
private Xid xid;
/**
* Constructor
* @param resourceImpl the EhcacheXAResourceImpl this Request will be used for
* @param request the actual Request
* @param xid
*/
public XARequestCallable(EhcacheXAResourceImpl resourceImpl, XARequest request, Xid xid) {
this.resourceImpl = resourceImpl;
this.request = request;
this.xid = xid;
}
/**
*
*/
public XAResponse call() throws Exception {
Thread.currentThread().setName("XA-Request processor Thread Xid [ " + xid + " ]");
int returnFlag = XAResource.TMNOFLAGS;
XAException xaException = null;
try {
switch(request.getRequestType()) {
case FORGET:
resourceImpl.forgetInternal(request.getXid());
break;
case PREPARE:
returnFlag = resourceImpl.prepareInternal(request.getXid());
break;
case ROLLBACK:
resourceImpl.rollbackInternal(request.getXid());
break;
case COMMIT:
resourceImpl.commitInternal(request.getXid(), request.isOnePhase());
break;
default:
throw new EhcacheXAException("Unknown enum type: " + request.getRequestType(), XAException.XAER_RMERR);
}
} catch (XAException xaE) {
xaException = xaE;
}
return new XAResponse(returnFlag, xaException);
}
}
/**
*
* @author nelrahma
*
*/
private static class XAResponse {
private final int flags;
private final XAException xaException;
/**
* Constructor
* @param flags flags returned by the actual call against the XAResource
* @param xaException Exception thrown by the call, otherwise null
*/
public XAResponse(int flags, XAException xaException) {
this.flags = flags;
this.xaException = xaException;
}
/**
* Gets the flags returned by the actual call against the XAResource
* @return the flags
*/
public int getFlags() {
return flags;
}
/**
* Gets the Exception thrown by the actual call against the XAResource
* @return the exception, null if none
*/
public XAException getXaException() {
return xaException;
}
}
}