/*
* Copyright 2002-2018 the original author or authors.
*
* 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 org.springframework.jca.endpoint;
import java.lang.reflect.Method;
import javax.resource.ResourceException;
import javax.resource.spi.ApplicationServerInternalException;
import javax.resource.spi.UnavailableException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.lang.Nullable;
import org.springframework.transaction.jta.SimpleTransactionFactory;
import org.springframework.transaction.jta.TransactionFactory;
import org.springframework.util.Assert;
Abstract base implementation of the JCA 1.7 MessageEndpointFactory
interface, providing transaction management capabilities as well as ClassLoader exposure for endpoint invocations. Author: Juergen Hoeller See Also: Since: 2.5
/**
* Abstract base implementation of the JCA 1.7
* {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
* providing transaction management capabilities as well as ClassLoader
* exposure for endpoint invocations.
*
* @author Juergen Hoeller
* @since 2.5
* @see #setTransactionManager
*/
public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory, BeanNameAware {
Logger available to subclasses. /** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@Nullable
private TransactionFactory transactionFactory;
@Nullable
private String transactionName;
private int transactionTimeout = -1;
@Nullable
private String beanName;
Set the XA transaction manager to use for wrapping endpoint
invocations, enlisting the endpoint resource in each such transaction.
The passed-in object may be a transaction manager which implements Spring's TransactionFactory
interface, or a plain TransactionManager
.
If no transaction manager is specified, the endpoint invocation
will simply not be wrapped in an XA transaction. Check out your
resource provider's ActivationSpec documentation for local
transaction options of your particular provider.
See Also:
/**
* Set the XA transaction manager to use for wrapping endpoint
* invocations, enlisting the endpoint resource in each such transaction.
* <p>The passed-in object may be a transaction manager which implements
* Spring's {@link org.springframework.transaction.jta.TransactionFactory}
* interface, or a plain {@link javax.transaction.TransactionManager}.
* <p>If no transaction manager is specified, the endpoint invocation
* will simply not be wrapped in an XA transaction. Check out your
* resource provider's ActivationSpec documentation for local
* transaction options of your particular provider.
* @see #setTransactionName
* @see #setTransactionTimeout
*/
public void setTransactionManager(Object transactionManager) {
if (transactionManager instanceof TransactionFactory) {
this.transactionFactory = (TransactionFactory) transactionManager;
}
else if (transactionManager instanceof TransactionManager) {
this.transactionFactory = new SimpleTransactionFactory((TransactionManager) transactionManager);
}
else {
throw new IllegalArgumentException("Transaction manager [" + transactionManager +
"] is neither a [org.springframework.transaction.jta.TransactionFactory} nor a " +
"[javax.transaction.TransactionManager]");
}
}
Set the Spring TransactionFactory to use for wrapping endpoint
invocations, enlisting the endpoint resource in each such transaction.
Alternatively, specify an appropriate transaction manager through the "transactionManager"
property.
If no transaction factory is specified, the endpoint invocation
will simply not be wrapped in an XA transaction. Check out your
resource provider's ActivationSpec documentation for local
transaction options of your particular provider.
See Also:
/**
* Set the Spring TransactionFactory to use for wrapping endpoint
* invocations, enlisting the endpoint resource in each such transaction.
* <p>Alternatively, specify an appropriate transaction manager through
* the {@link #setTransactionManager "transactionManager"} property.
* <p>If no transaction factory is specified, the endpoint invocation
* will simply not be wrapped in an XA transaction. Check out your
* resource provider's ActivationSpec documentation for local
* transaction options of your particular provider.
* @see #setTransactionName
* @see #setTransactionTimeout
*/
public void setTransactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
}
Specify the name of the transaction, if any.
Default is none. A specified name will be passed on to the transaction
manager, allowing to identify the transaction in a transaction monitor.
/**
* Specify the name of the transaction, if any.
* <p>Default is none. A specified name will be passed on to the transaction
* manager, allowing to identify the transaction in a transaction monitor.
*/
public void setTransactionName(String transactionName) {
this.transactionName = transactionName;
}
Specify the transaction timeout, if any.
Default is -1: rely on the transaction manager's default timeout.
Specify a concrete timeout to restrict the maximum duration of each
endpoint invocation.
/**
* Specify the transaction timeout, if any.
* <p>Default is -1: rely on the transaction manager's default timeout.
* Specify a concrete timeout to restrict the maximum duration of each
* endpoint invocation.
*/
public void setTransactionTimeout(int transactionTimeout) {
this.transactionTimeout = transactionTimeout;
}
Set the name of this message endpoint. Populated with the bean name
automatically when defined within Spring's bean factory.
/**
* Set the name of this message endpoint. Populated with the bean name
* automatically when defined within Spring's bean factory.
*/
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
Implementation of the JCA 1.7 #getActivationName()
method, returning the bean name as set on this MessageEndpointFactory. See Also:
/**
* Implementation of the JCA 1.7 {@code #getActivationName()} method,
* returning the bean name as set on this MessageEndpointFactory.
* @see #setBeanName
*/
@Override
@Nullable
public String getActivationName() {
return this.beanName;
}
Implementation of the JCA 1.7 #getEndpointClass()
method, returning
null in order to indicate a synthetic endpoint type. /**
* Implementation of the JCA 1.7 {@code #getEndpointClass()} method,
* returning {@code} null in order to indicate a synthetic endpoint type.
*/
@Override
@Nullable
public Class<?> getEndpointClass() {
return null;
}
This implementation returns true
if a transaction manager has been specified; false
otherwise. See Also:
/**
* This implementation returns {@code true} if a transaction manager
* has been specified; {@code false} otherwise.
* @see #setTransactionManager
* @see #setTransactionFactory
*/
@Override
public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException {
return (this.transactionFactory != null);
}
The standard JCA 1.5 version of createEndpoint
. This implementation delegates to createEndpointInternal()
, initializing the endpoint's XAResource before the endpoint gets invoked.
/**
* The standard JCA 1.5 version of {@code createEndpoint}.
* <p>This implementation delegates to {@link #createEndpointInternal()},
* initializing the endpoint's XAResource before the endpoint gets invoked.
*/
@Override
public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
AbstractMessageEndpoint endpoint = createEndpointInternal();
endpoint.initXAResource(xaResource);
return endpoint;
}
The alternative JCA 1.6 version of createEndpoint
. This implementation delegates to createEndpointInternal()
, ignoring the specified timeout. It is only here for JCA 1.6 compliance.
/**
* The alternative JCA 1.6 version of {@code createEndpoint}.
* <p>This implementation delegates to {@link #createEndpointInternal()},
* ignoring the specified timeout. It is only here for JCA 1.6 compliance.
*/
@Override
public MessageEndpoint createEndpoint(XAResource xaResource, long timeout) throws UnavailableException {
AbstractMessageEndpoint endpoint = createEndpointInternal();
endpoint.initXAResource(xaResource);
return endpoint;
}
Create the actual endpoint instance, as a subclass of the AbstractMessageEndpoint
inner class of this factory. Throws: - UnavailableException – if no endpoint is available at present
Returns: the actual endpoint instance (never null
)
/**
* Create the actual endpoint instance, as a subclass of the
* {@link AbstractMessageEndpoint} inner class of this factory.
* @return the actual endpoint instance (never {@code null})
* @throws UnavailableException if no endpoint is available at present
*/
protected abstract AbstractMessageEndpoint createEndpointInternal() throws UnavailableException;
Inner class for actual endpoint implementations, based on template
method to allow for any kind of concrete endpoint implementation.
/**
* Inner class for actual endpoint implementations, based on template
* method to allow for any kind of concrete endpoint implementation.
*/
protected abstract class AbstractMessageEndpoint implements MessageEndpoint {
@Nullable
private TransactionDelegate transactionDelegate;
private boolean beforeDeliveryCalled = false;
@Nullable
private ClassLoader previousContextClassLoader;
Initialize this endpoint's TransactionDelegate.
Params: - xaResource – the XAResource for this endpoint
/**
* Initialize this endpoint's TransactionDelegate.
* @param xaResource the XAResource for this endpoint
*/
void initXAResource(XAResource xaResource) {
this.transactionDelegate = new TransactionDelegate(xaResource);
}
This beforeDelivery
implementation starts a transaction, if necessary, and exposes the endpoint ClassLoader as current thread context ClassLoader. Note that the JCA 1.7 specification does not require a ResourceAdapter to call this method before invoking the concrete endpoint. If this method has not been called (check hasBeforeDeliveryBeenCalled()
), the concrete endpoint method should call beforeDelivery
and its sibling afterDelivery()
explicitly, as part of its own processing.
/**
* This {@code beforeDelivery} implementation starts a transaction,
* if necessary, and exposes the endpoint ClassLoader as current
* thread context ClassLoader.
* <p>Note that the JCA 1.7 specification does not require a ResourceAdapter
* to call this method before invoking the concrete endpoint. If this method
* has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the
* concrete endpoint method should call {@code beforeDelivery} and its
* sibling {@link #afterDelivery()} explicitly, as part of its own processing.
*/
@Override
public void beforeDelivery(@Nullable Method method) throws ResourceException {
this.beforeDeliveryCalled = true;
Assert.state(this.transactionDelegate != null, "Not initialized");
try {
this.transactionDelegate.beginTransaction();
}
catch (Throwable ex) {
throw new ApplicationServerInternalException("Failed to begin transaction", ex);
}
Thread currentThread = Thread.currentThread();
this.previousContextClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader(getEndpointClassLoader());
}
Template method for exposing the endpoint's ClassLoader
(typically the ClassLoader that the message listener class
has been loaded with).
Returns: the endpoint ClassLoader (never null
)
/**
* Template method for exposing the endpoint's ClassLoader
* (typically the ClassLoader that the message listener class
* has been loaded with).
* @return the endpoint ClassLoader (never {@code null})
*/
protected abstract ClassLoader getEndpointClassLoader();
Return whether the beforeDelivery
method of this endpoint has already been called. /**
* Return whether the {@link #beforeDelivery} method of this endpoint
* has already been called.
*/
protected final boolean hasBeforeDeliveryBeenCalled() {
return this.beforeDeliveryCalled;
}
Callback method for notifying the endpoint base class
that the concrete endpoint invocation led to an exception.
To be invoked by subclasses in case of the concrete
endpoint throwing an exception.
Params: - ex – the exception thrown from the concrete endpoint
/**
* Callback method for notifying the endpoint base class
* that the concrete endpoint invocation led to an exception.
* <p>To be invoked by subclasses in case of the concrete
* endpoint throwing an exception.
* @param ex the exception thrown from the concrete endpoint
*/
protected void onEndpointException(Throwable ex) {
Assert.state(this.transactionDelegate != null, "Not initialized");
this.transactionDelegate.setRollbackOnly();
logger.debug("Transaction marked as rollback-only after endpoint exception", ex);
}
This afterDelivery
implementation resets the thread context ClassLoader and completes the transaction, if any. Note that the JCA 1.7 specification does not require a ResourceAdapter to call this method after invoking the concrete endpoint. See the explanation in beforeDelivery
's javadoc.
/**
* This {@code afterDelivery} implementation resets the thread context
* ClassLoader and completes the transaction, if any.
* <p>Note that the JCA 1.7 specification does not require a ResourceAdapter
* to call this method after invoking the concrete endpoint. See the
* explanation in {@link #beforeDelivery}'s javadoc.
*/
@Override
public void afterDelivery() throws ResourceException {
Assert.state(this.transactionDelegate != null, "Not initialized");
this.beforeDeliveryCalled = false;
Thread.currentThread().setContextClassLoader(this.previousContextClassLoader);
this.previousContextClassLoader = null;
try {
this.transactionDelegate.endTransaction();
}
catch (Throwable ex) {
logger.warn("Failed to complete transaction after endpoint delivery", ex);
throw new ApplicationServerInternalException("Failed to complete transaction", ex);
}
}
@Override
public void release() {
if (this.transactionDelegate != null) {
try {
this.transactionDelegate.setRollbackOnly();
this.transactionDelegate.endTransaction();
}
catch (Throwable ex) {
logger.warn("Could not complete unfinished transaction on endpoint release", ex);
}
}
}
}
Private inner class that performs the actual transaction handling,
including enlistment of the endpoint's XAResource.
/**
* Private inner class that performs the actual transaction handling,
* including enlistment of the endpoint's XAResource.
*/
private class TransactionDelegate {
@Nullable
private final XAResource xaResource;
@Nullable
private Transaction transaction;
private boolean rollbackOnly;
public TransactionDelegate(@Nullable XAResource xaResource) {
if (xaResource == null && transactionFactory != null &&
!transactionFactory.supportsResourceAdapterManagedTransactions()) {
throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " +
"transaction management. Check your ResourceAdapter's configuration.");
}
this.xaResource = xaResource;
}
public void beginTransaction() throws Exception {
if (transactionFactory != null && this.xaResource != null) {
this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout);
this.transaction.enlistResource(this.xaResource);
}
}
public void setRollbackOnly() {
if (this.transaction != null) {
this.rollbackOnly = true;
}
}
public void endTransaction() throws Exception {
if (this.transaction != null) {
try {
if (this.rollbackOnly) {
this.transaction.rollback();
}
else {
this.transaction.commit();
}
}
finally {
this.transaction = null;
this.rollbackOnly = false;
}
}
}
}
}