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}