Frank, Andreas,
Thanks for the replies. I was able to rework the cache flush
broadcaster to not hold onto the session and that seems to work. At
this point it only holds onto the TCF and the Topic and does everything
needed to send a message. I'll try to answer Andreas' questions below
and will also try to send some code as well.
We are not using EJBs at all. The web app framework (Struts2) objects
(Actions) have services injected into them from Spring. Services are
just Spring beans (singletons) with a transaction interceptor. So the
thread will start a transaction, do some work in the Service/DAO - the
DAO will use Hibernate and the way that Spring works, it will flush the
Hibernate session as part of a transaction synchronization (preCommit).
When Hibernate flushes, it will update its second level cache and that
fires off a cache flush notification. We get notifications for all
entities changed within the transaction so we end up sending many
messages all within the scope of the transaction synchronization object.
With the existing code, the session was transactional so the same one is
used and if there was a second entity updated in the transaction, then I
would get the state exception below.
You can see the base code for the listener in OSCache. EHCache has a
similar beast. I've extended the OSCache code significantly in order
to use it with Spring/Hibernate (but not in its usage of JMS objects).
It would be interesting to try this with EHCache.
I'm using the 6.0.1-SNAPSHOT RAR (the download from the main website).
It looks like the code has changed a bit since then but I've not tried
to get the code and build it.
The following is the Ant build snippet that deploys the RAR and
sets up the JMS resoruces:
<target name="sun-jms-adapter-deploy" description="deploy the RAR">
<sun-appserv-admin command="create-resource-adapter-config sun-jms-adapter" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
<sun-appserv-deploy file="${env.APPSERVER_HOME}/lib/addons/resourceadapters/sun-jms-adapter/sun-jms-adapter.rar" name="sun-jms-adapter" upload="true" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
</target>
<target name="resources-jms-create" description="create JMS resources">
<sun-appserv-admin command="create-connector-connection-pool --raname sun-jms-adapter --connectiondefinition javax.jms.TopicConnectionFactory --steadypoolsize 0 --poolresize 4 --idletimeout 25000 --transactionsupport XATransaction --isconnectvalidatereq=true --property UserName=admin:Password=admin:ConnectionURL=mq\\://localhost\\:7676/ jms/CmmoTopicConnectionFactory" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
<sun-appserv-admin command="create-connector-resource --poolname jms/CmmoTopicConnectionFactory jms/CmmoTopicConnectionFactory" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
<sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoCacheFlushTopicDestination jms/CmmoCacheFlushTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
<sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoImportBundlesTopicDestination jms/CmmoImportBundlesTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
<sun-appserv-admin command="create-admin-object --restype javax.jms.Topic --raname sun-jms-adapter --property Name=CmmoImportAssetsTopicDestination jms/CmmoImportAssetsTopic" user="admin" passwordfile="${user.home}/ant/glassfish.properties" asinstalldir="${env.APPSERVER_HOME}" />
</target>
You'll notice that we aren't using ProducerPooling since this seems
to be deprecated with the version 6 adapter.
As for Spring, we have 2 use cases - one that is pure Spring and does
seem to work with the Containers - and another where we use Spring
classes directly. That last one fails but it failed without Spring
classes as well (straight JMS).
For the one that works we are doing the following:
<bean id="cmmo_jmsImportBundlesContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="cmmo_topicConnectionFactory"/>
<property name="destination" ref="cmmo_bundleDestination"/>
<property name="messageListener" ref="cmmo_importBundlesListener" />
</bean>
<bean id="cmmo_importBundlesJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="cmmo_topicConnectionFactory"/>
<property name="defaultDestination" ref="cmmo_bundleDestination" />
</bean>
<bean id="cmmo_bundleDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${jms.topic.bundle_import}"/>
</bean>
<bean id="cmmo_importBundlesSender" class="com.biperf.cmmo.service.message.async.CmmoImportBundlesSender">
<property name="jmsTemplate" ref="cmmo_importBundlesJmsTemplate"/>
</bean>
<bean id="cmmo_importBundlesListener" class="com.biperf.cmmo.service.message.async.CmmoImportBundlesListener">
<property name="messageService" ref="cmmo_messageService" />
</bean>
<bean id="cmmo_topicConnectionFactory" class="org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy">
<property name="targetConnectionFactory" ref="internalJmsTopicConnectionFactory"/>
</bean>
<bean id="internalJmsTopicConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${jms.topic.factory}"/>
</bean>
Andreas - you mentioned that this wouldn't work but it does for us
with Spring 2.5.3 and JMSJCA 6.0.1.
For the scenario that doesn't work, we have code that is based on
the JMSBroadcastListener within OSCache as per
http://www.opensymphony.com/oscache/wiki/Clustering.html
This code holds onto (as instance variables) a Connection, Session, MessageProducer
and MessageConsumer. We end up doing the following in the init method.
createJmsObjects( topicFactoryName, topicName );
consumer.setMessageListener( createMessageListener() );
connection.start();
Where createJmsObjects does the following:
InitialContext jndi = getInitialContext();
TopicConnectionFactory connectionFactory = (TopicConnectionFactory)jndi.lookup( topicFactoryName );
connection = connectionFactory.createConnection();
session = connection.createSession( false, Session.AUTO_ACKNOWLEDGE );
Topic topic = (Topic)jndi.lookup( topicName );
publisher = session.createProducer( topic );
consumer = session.createConsumer( topic );
and createMessageListener does:
protected MessageListener createMessageListener()
{
return new MessageListener()
{
public void onMessage( Message message )
{
try
{
// check the message type
ObjectMessage objectMessage = null;
if ( ! ( message instanceof ObjectMessage ) )
{
log.error( "Cannot handle message of type (class=" + message.getClass().getName() + "). Notification ignored." );
return;
}
objectMessage = (ObjectMessage)message;
// check the message content
if ( ! ( objectMessage.getObject() instanceof ClusterNotification ) )
{
log.error( "An unknown cluster notification message received (class=" + objectMessage.getObject().getClass().getName() + "). Notification ignored." );
return;
}
if ( log.isDebugEnabled() )
{
log.debug( objectMessage.getObject() );
}
String incomingClusterNode = objectMessage.getStringProperty( "nodeName" );
// This prevents the notification sent by this node from being handled by itself
if ( doClusterNotification( incomingClusterNode ) )
{
// now handle the message
ClusterNotification notification = (ClusterNotification)objectMessage.getObject();
handleClusterNotification( notification );
}
}
catch( NullPointerException npe )
{
// ignore this exception. If this is a flushGroup and the group is not defined - we get a
// NullPointerException. Do not log anything in this case.
}
catch( Exception jmsEx )
{
log.error( "Cannot handle cluster Notification", jmsEx );
}
}
};
}
Sending a cache flush notification looks like:
protected void sendNotification( ClusterNotification message )
{
try
{
ObjectMessage objectMessage = session.createObjectMessage();
objectMessage.setObject( message );
//sign the message, with the name of this node
objectMessage.setStringProperty( "nodeName", clusterNode );
publisher.send( objectMessage );
}
catch( JMSException e )
{
log.error( "Cannot send notification " + message, e );
}
}
and again, if I did this code 2 times in a transaction, then I would
get the state issue.
Again, I've worked around this by changing what objects I was holding
on to but I'm still curious about what is the best way to setup something
like this (again, EHCache does something similar).
I'll try to put together a small sample application and double-check
the OSCache codebase to see if they have changed their code.
Brian
----- Original message -----
From: "Andreas Loew" <Andreas.Loew_at_Sun.COM>
To: users_at_jmsjca.dev.java.net
Date: Mon, 05 Jan 2009 23:24:02 +0100
Subject: Re: Sessions are already closed
Brian, Frank,
let's see if I am able to jump in here, assuming that I am one of
probably only a few people within Sun who has used JMSJCA in conjunction
with Spring in quite some depth in a "real-world" customer scenario ;-)
(Sorry, I also missed the original mail from December due to an extended
Christmas vacation...)
Frank Kieviet schrieb:
> I'm not very familiar with Spring, and the stack trace doesn't look
> like a known problem. Rather it looks like a problem in Spring. Do you
> still have this issue?
I wouldn't assume an issue with Spring in the first place.
Rather, I tend to think that the usage pattern and/or Spring feature(s)
as used by Brian might not be fully appropriate to the usage scenario:
Brian, please find my detailed set of questions/requests inline... :-)
> ----------------------------------------------------------------
> --------
> *From:* Brian Repko [mailto:brianrepko_at_fastmail.us] *Subject:*
> Sessions are already closed
> I'm using the sun-jms-connector as a standard RAR (global)
> against which I send and receive cache flush notifications
> (listening on a topic).
> I continue to get JMSJCA-E153 messages (session is closed) when trying
> to send the notification. These cache flush notifications are
> generated by Hibernate (second level cache) and are part of a
> transaction synchronization.
> The event handler is OSCache which then delegates to my code to send
> the message. In my message sender, I have a Spring JmsTemplate that
> has the TopicConnectionFactory and the Topic.
If I got you right, this means that, at any point in time, an arbitrary
thread from the Java EE container (BTW: is your app using Hibernate from
the web container or the EJB container?) might call into your code
sending and receiving a cache flush notifications?
So please forward some more details about the setup of your app:
- the definition of your JMSJCA connection pools/resources in Glassfish
- your Spring application context
and some details on how you are using the JMSJCA TCF and/or other Spring
features from within your application code.
As a starting point, here is a snippet of one of my application contexts
that is proven to successfully send transactional messages using JMSJCA
5.1.3 from the Sun App Server 8.1 web container:
<bean id="jmsTopicConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="resourceRef"> <value>true</value>
</property> <property name="jndiName">
<value>jms/XAOutTopicCF</value> </property> </bean>
<bean id="jmsTopicTemplate"
class="org.springframework.jms.core.JmsTemplate"> <property
name="connectionFactory" ref="jmsTopicConnectionFactory" />
<property name="pubSubDomain" value="true" /> </bean>
<bean id="jmsDestination"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="resourceRef"> <value>true</value>
</property> <property name="jndiName">
<value>jms/OutboundTopic</value> </property> </bean>
<bean id="messageSender" class="com.sun.germany.vine2.wsn.consumer-
.jms.JmsMessageSenderImpl"> <constructor-arg ref="jmsTemplate"
/> <constructor-arg ref="jmsDestination" /> </bean>
The respective pool/resource definitions are as follows:
$ASADMIN create-connector-connection-pool --echo --user admin \ --
host $HOST --port $PORT --secure \ --passwordfile $PASSWORD_FILE
--steadypoolsize 4 \ --maxpoolsize 32 --poolresize 2 --maxwait
60000 \ --idletimeout 3300 --failconnection=false --raname
raunifiedjms \ --transactionsupport XATransaction \ --
connectiondefinition javax.jms.TopicConnectionFactory \ --
description "JMSJCA XA Outbound Topic Connection Pool" \ --property
ConnectionURL=${JMS_URL}:\ UserName=admin:Password=admin:\
ProducerPooling=true:IdleTimeout=86400000 \ jms/XAOutTopicPool
$ASADMIN create-connector-resource --echo --user admin \ --host
$HOST --port $PORT --secure \ --passwordfile $PASSWORD_FILE \
--target $TARGET \ --description "JMSJCA XA Outbound Topic
Connection Factory" \ --poolname jms/XAOutTopicPool
jms/XAOutTopicCF
$ASADMIN create-admin-object --echo --user admin \ --host $HOST --
port $PORT --secure \ --passwordfile $PASSWORD_FILE \ --raname
raunifiedjms --restype javax.jms.Topic \ --description "Outbound
JMS Topic" \ --property Name=yourDesiredTopicName \ --target
$TARGET jms/OutboundTopic
> I'm wondering if there is an option that folks know about for keeping
> the session open (or forcing a new session). I'm not sure about
> ProducerPooling and will try that.
Yes, you should definitely use JMSJCA ProducerPooling in your JMSJCA
connection pool setup in order to make JMSJCA do exactly what the option
says: pool JMS MessageProducers (QueueSeners, TopicPublishers).
To pool JMS Connections and JMS Sessions is the job of the JMSJCA
resource adapter and the JMS resources referring to it as defined in
Glassfish.
Also, please be warned that in order to do any inbound message
processing from the web container, you should definitely use the JCA
support that comes with Spring >= 2.5 (JmsMessageEndpointManager) and
not try to fiddle with the MessageListenerContainer approach (as the
latter effectively only works in "JMSJCA.BypassRA=true" mode).
Based on your application context and Glassfish resource definitions, I
hope to be able to provide more specific advice...
Hope this helps & best regards,
Andreas
--
Andreas Loew
Senior Java Architect
Sun Microsystems (Germany)
---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe_at_jmsjca.dev.java.net
For additional commands, e-mail: users-help_at_jmsjca.dev.java.net