/*
* Copyright 2006-2007 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.batch.container.jms;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.interceptor.RepeatOperationsInterceptor;
import org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;
/**
* Message listener container adapted for intercepting the message reception
* with advice provided through configuration.<br>
*
* To enable batching of messages in a single transaction, use the
* {@link TransactionInterceptor} and the {@link RepeatOperationsInterceptor} in
* the advice chain (with or without a transaction manager set in the base
* class). Instead of receiving a single message and processing it, the
* container will then use a {@link RepeatOperations} to receive multiple
* messages in the same thread. Use with a {@link RepeatOperations} and a
* transaction interceptor. If the transaction interceptor uses XA then use an
* XA connection factory, or else the
* {@link TransactionAwareConnectionFactoryProxy} to synchronize the JMS session
* with the ongoing transaction (opening up the possibility of duplicate
* messages after a failure). In the latter case you will not need to provide a
* transaction manager in the base class - it only gets on the way and prevents
* the JMS session from synchronizing with the database transaction.
*
* @author Dave Syer
*
*/
public class BatchMessageListenerContainer extends DefaultMessageListenerContainer {
/**
* @author Dave Syer
*
*/
public static interface ContainerDelegate {
boolean receiveAndExecute(Object invoker, Session session, MessageConsumer consumer) throws JMSException;
}
private Advice[] advices = new Advice[0];
private ContainerDelegate delegate = new ContainerDelegate() {
@Override
public boolean receiveAndExecute(Object invoker, Session session, MessageConsumer consumer) throws JMSException {
return BatchMessageListenerContainer.super.receiveAndExecute(invoker, session, consumer);
}
};
private ContainerDelegate proxy = delegate;
/**
* Public setter for the {@link Advice}.
* @param advices the advice to set
*/
public void setAdviceChain(Advice[] advices) {
this.advices = advices;
}
/**
* Set up interceptor with provided advice on the
* {@link #receiveAndExecute(Object, Session, MessageConsumer)} method.
*
* @see org.springframework.jms.listener.AbstractJmsListeningContainer#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
initializeProxy();
}
/**
* Override base class to prevent exceptions from being swallowed. Should be
* an injectable strategy (see SPR-4733).
*
* @see org.springframework.jms.listener.AbstractMessageListenerContainer#handleListenerException(java.lang.Throwable)
*/
@Override
protected void handleListenerException(Throwable ex) {
if (!isSessionTransacted()) {
// Log the exceptions in base class if not transactional anyway
super.handleListenerException(ex);
return;
}
logger.debug("Re-throwing exception in container.");
if (ex instanceof RuntimeException) {
// We need to re-throw so that an enclosing non-JMS transaction can
// rollback...
throw (RuntimeException) ex;
}
else if (ex instanceof Error) {
// Just re-throw Error instances because otherwise unit tests just
// swallow exceptions from EasyMock and JUnit.
throw (Error) ex;
}
}
/**
* Override base class method to wrap call in advice if provided.
* @see org.springframework.jms.listener.AbstractPollingMessageListenerContainer#receiveAndExecute(Object,
* javax.jms.Session, javax.jms.MessageConsumer)
*/
@Override
protected boolean receiveAndExecute(final Object invoker, final Session session, final MessageConsumer consumer)
throws JMSException {
return proxy.receiveAndExecute(invoker, session, consumer);
}
/**
*
*/
public void initializeProxy() {
ProxyFactory factory = new ProxyFactory();
for (int i = 0; i < advices.length; i++) {
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(advices[i]);
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("receiveAndExecute");
advisor.setPointcut(pointcut);
factory.addAdvisor(advisor);
}
factory.setProxyTargetClass(false);
factory.addInterface(ContainerDelegate.class);
factory.setTarget(delegate);
proxy = (ContainerDelegate) factory.getProxy();
}
}