001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.ra; 018 019import java.lang.reflect.Method; 020 021import javax.jms.JMSException; 022import javax.jms.Message; 023import javax.jms.MessageListener; 024import javax.jms.MessageProducer; 025import javax.jms.ServerSession; 026import javax.jms.Session; 027import javax.resource.spi.endpoint.MessageEndpoint; 028import javax.resource.spi.work.Work; 029import javax.resource.spi.work.WorkEvent; 030import javax.resource.spi.work.WorkException; 031import javax.resource.spi.work.WorkListener; 032import javax.resource.spi.work.WorkManager; 033 034import org.apache.activemq.ActiveMQSession; 035import org.apache.activemq.ActiveMQSession.DeliveryListener; 036import org.apache.activemq.TransactionContext; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * 042 */ 043public class ServerSessionImpl implements ServerSession, InboundContext, Work, DeliveryListener { 044 045 public static final Method ON_MESSAGE_METHOD; 046 private static int nextLogId; 047 048 static { 049 try { 050 ON_MESSAGE_METHOD = MessageListener.class.getMethod("onMessage", new Class[] { 051 Message.class 052 }); 053 } catch (Exception e) { 054 throw new ExceptionInInitializerError(e); 055 } 056 } 057 058 059 private final Logger log = LoggerFactory.getLogger(ServerSessionImpl.class); 060 061 private ActiveMQSession session; 062 private WorkManager workManager; 063 private MessageEndpoint endpoint; 064 private MessageProducer messageProducer; 065 private final ServerSessionPoolImpl pool; 066 067 private Object runControlMutex = new Object(); 068 private boolean runningFlag; 069 /** 070 * True if an error was detected that cause this session to be stale. When a 071 * session is stale, it should not be used again for proccessing. 072 */ 073 private boolean stale; 074 /** 075 * Does the TX commit need to be managed by the RA? 076 */ 077 private final boolean useRAManagedTx; 078 /** 079 * The maximum number of messages to batch 080 */ 081 private final int batchSize; 082 /** 083 * The current number of messages in the batch 084 */ 085 private int currentBatchSize; 086 087 public ServerSessionImpl(ServerSessionPoolImpl pool, ActiveMQSession session, WorkManager workManager, MessageEndpoint endpoint, boolean useRAManagedTx, int batchSize) throws JMSException { 088 this.pool = pool; 089 this.session = session; 090 this.workManager = workManager; 091 this.endpoint = endpoint; 092 this.useRAManagedTx = useRAManagedTx; 093 this.session.setMessageListener((MessageListener)endpoint); 094 this.session.setDeliveryListener(this); 095 this.batchSize = batchSize; 096 } 097 098 private static synchronized int getNextLogId() { 099 return nextLogId++; 100 } 101 102 public Session getSession() throws JMSException { 103 return session; 104 } 105 106 protected boolean isStale() { 107 return stale || !session.isRunning(); 108 } 109 110 public MessageProducer getMessageProducer() throws JMSException { 111 if (messageProducer == null) { 112 messageProducer = getSession().createProducer(null); 113 } 114 return messageProducer; 115 } 116 117 /** 118 * @see javax.jms.ServerSession#start() 119 */ 120 public void start() throws JMSException { 121 122 synchronized (runControlMutex) { 123 if (runningFlag) { 124 log.debug("Start request ignored, already running."); 125 return; 126 } 127 runningFlag = true; 128 } 129 130 // We get here because we need to start a async worker. 131 log.debug("Starting run."); 132 try { 133 workManager.scheduleWork(this, WorkManager.INDEFINITE, null, new WorkListener() { 134 // The work listener is useful only for debugging... 135 public void workAccepted(WorkEvent event) { 136 log.debug("Work accepted: " + event); 137 } 138 139 public void workRejected(WorkEvent event) { 140 log.debug("Work rejected: " + event); 141 } 142 143 public void workStarted(WorkEvent event) { 144 log.debug("Work started: " + event); 145 } 146 147 public void workCompleted(WorkEvent event) { 148 log.debug("Work completed: " + event); 149 } 150 151 }); 152 } catch (WorkException e) { 153 throw (JMSException)new JMSException("Start failed: " + e).initCause(e); 154 } 155 } 156 157 /** 158 * @see java.lang.Runnable#run() 159 */ 160 public void run() { 161 log.debug("{} Running", this); 162 currentBatchSize = 0; 163 while (true) { 164 log.debug("{} run loop", this); 165 try { 166 InboundContextSupport.register(this); 167 if (session.isClosed()) { 168 stale = true; 169 } else if (session.isRunning() ) { 170 session.run(); 171 } else { 172 log.debug("JMS Session {} with unconsumed {} is no longer running (maybe due to loss of connection?), marking ServerSession as stale", session, session.getUnconsumedMessages().size()); 173 stale = true; 174 } 175 } catch (Throwable e) { 176 stale = true; 177 if ( log.isDebugEnabled() ) { 178 log.debug("Endpoint {} failed to process message.", this, e); 179 } else if ( log.isInfoEnabled() ) { 180 log.info("Endpoint {} failed to process message. Reason: " + e.getMessage(), this); 181 } 182 } finally { 183 InboundContextSupport.unregister(this); 184 log.debug("run loop end"); 185 synchronized (runControlMutex) { 186 // This endpoint may have gone stale due to error 187 if (stale) { 188 log.debug("Session {} stale, removing from pool", this); 189 runningFlag = false; 190 pool.removeFromPool(this); 191 break; 192 } 193 if (!session.hasUncomsumedMessages()) { 194 runningFlag = false; 195 log.debug("Session {} has no unconsumed message, returning to pool", this); 196 pool.returnToPool(this); 197 break; 198 } else { 199 log.debug("Session has session has more work to do b/c of unconsumed", this); 200 } 201 } 202 } 203 } 204 log.debug("{} Run finished", this); 205 } 206 207 /** 208 * The ActiveMQSession's run method will call back to this method before 209 * dispactching a message to the MessageListener. 210 */ 211 public void beforeDelivery(ActiveMQSession session, Message msg) { 212 if (currentBatchSize == 0) { 213 try { 214 endpoint.beforeDelivery(ON_MESSAGE_METHOD); 215 } catch (Throwable e) { 216 throw new RuntimeException("Endpoint before delivery notification failure", e); 217 } 218 } 219 } 220 221 /** 222 * The ActiveMQSession's run method will call back to this method after 223 * dispactching a message to the MessageListener. 224 */ 225 public void afterDelivery(ActiveMQSession session, Message msg) { 226 if (++currentBatchSize >= batchSize || !session.hasUncomsumedMessages()) { 227 currentBatchSize = 0; 228 try { 229 endpoint.afterDelivery(); 230 } catch (Throwable e) { 231 throw new RuntimeException("Endpoint after delivery notification failure: " + e, e); 232 } finally { 233 TransactionContext transactionContext = session.getTransactionContext(); 234 if (transactionContext != null && transactionContext.isInLocalTransaction()) { 235 if (!useRAManagedTx) { 236 // Sanitiy Check: If the local transaction has not been 237 // commited.. 238 // Commit it now. 239 log.warn("Local transaction had not been commited. Commiting now."); 240 } 241 try { 242 session.commit(); 243 } catch (JMSException e) { 244 log.info("Commit failed:", e); 245 } 246 } 247 } 248 } 249 } 250 251 /** 252 * @see javax.resource.spi.work.Work#release() 253 */ 254 public void release() { 255 log.debug("release called"); 256 } 257 258 /** 259 * @see java.lang.Object#toString() 260 */ 261 @Override 262 public String toString() { 263 return "ServerSessionImpl:{" + session +"}"; 264 } 265 266 public void close() { 267 try { 268 endpoint.release(); 269 } catch (Throwable e) { 270 log.debug("Endpoint did not release properly: " + e.getMessage(), e); 271 } 272 try { 273 session.close(); 274 } catch (Throwable e) { 275 log.debug("Session did not close properly: " + e.getMessage(), e); 276 } 277 } 278 279}