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.io.Serializable;
020import java.net.URI;
021import java.util.Arrays;
022import java.util.HashMap;
023import java.util.concurrent.atomic.AtomicBoolean;
024
025import javax.jms.JMSException;
026import javax.resource.NotSupportedException;
027import javax.resource.ResourceException;
028import javax.resource.spi.ActivationSpec;
029import javax.resource.spi.BootstrapContext;
030import javax.resource.spi.ResourceAdapterInternalException;
031import javax.resource.spi.endpoint.MessageEndpointFactory;
032import javax.transaction.xa.XAException;
033import javax.transaction.xa.XAResource;
034import javax.transaction.xa.Xid;
035
036import org.apache.activemq.ActiveMQConnection;
037import org.apache.activemq.ActiveMQConnectionFactory;
038import org.apache.activemq.RedeliveryPolicy;
039import org.apache.activemq.TransactionContext;
040import org.apache.activemq.broker.BrokerFactory;
041import org.apache.activemq.broker.BrokerService;
042import org.apache.activemq.util.ServiceSupport;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import static org.apache.activemq.TransactionContext.toXAException;
047
048/**
049 * Knows how to connect to one ActiveMQ server. It can then activate endpoints
050 * and deliver messages to those end points using the connection configure in
051 * the resource adapter. <p/>Must override equals and hashCode (JCA spec 16.4)
052 *
053 * @org.apache.xbean.XBean element="resourceAdapter" rootElement="true"
054 *                         description="The JCA Resource Adaptor for ActiveMQ"
055 *
056 */
057public class ActiveMQResourceAdapter extends ActiveMQConnectionSupport implements Serializable, MessageResourceAdapter {
058    private static final long serialVersionUID = 360805587169336959L;
059    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQResourceAdapter.class);
060    private transient final HashMap<ActiveMQEndpointActivationKey, ActiveMQEndpointWorker> endpointWorkers = new HashMap<ActiveMQEndpointActivationKey, ActiveMQEndpointWorker>();
061    private final AtomicBoolean started = new AtomicBoolean(false);
062    private transient BootstrapContext bootstrapContext;
063    private String brokerXmlConfig;
064    private transient BrokerService broker;
065    private transient Thread brokerStartThread;
066    private ActiveMQConnectionFactory connectionFactory;
067    private transient ReconnectingXAResource reconnectingXaResource;
068
069    /**
070     *
071     */
072    public ActiveMQResourceAdapter() {
073        super();
074    }
075
076    /**
077     * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext)
078     */
079    @Override
080    public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
081        log.debug("Start: " + this.getInfo());
082        this.bootstrapContext = bootstrapContext;
083        if (brokerXmlConfig != null && brokerXmlConfig.trim().length() > 0) {
084            brokerStartThread = new Thread("Starting ActiveMQ Broker") {
085                @Override
086                public void run () {
087                    try {
088                        // ensure RAR resources are available to xbean (needed for weblogic)
089                        log.debug("original thread context classLoader: " + Thread.currentThread().getContextClassLoader());
090                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
091                        log.debug("current (from getClass()) thread context classLoader: " + Thread.currentThread().getContextClassLoader());
092
093                        synchronized( ActiveMQResourceAdapter.this ) {
094                            broker = BrokerFactory.createBroker(new URI(brokerXmlConfig));
095                        }
096                        broker.start();
097                        // Default the ServerUrl to the local broker if not specified in the ra.xml
098                        if (getServerUrl() == null) {
099                            setServerUrl("vm://" + broker.getBrokerName() + "?create=false");
100                        }
101                    } catch (Throwable e) {
102                        log.warn("Could not start up embeded ActiveMQ Broker '"+brokerXmlConfig+"': "+e.getMessage());
103                        log.debug("Reason for: "+e.getMessage(), e);
104                    }
105                }
106            };
107            brokerStartThread.setDaemon(true);
108            brokerStartThread.start();
109
110            // Wait up to 5 seconds for the broker to start up in the async thread.. otherwise keep going without it..
111            try {
112                brokerStartThread.join(1000*5);
113            } catch (InterruptedException e) {
114                Thread.currentThread().interrupt();
115            }
116        }
117        started.compareAndSet(false, true);
118    }
119
120    public ActiveMQConnection makeConnection() throws JMSException {
121        if( connectionFactory == null ) {
122            return makeConnection(getInfo());
123        } else {
124            return makeConnection(getInfo(), connectionFactory);
125        }
126    }
127
128    /**
129     * @param activationSpec
130     */
131    @Override
132    public ActiveMQConnection makeConnection(MessageActivationSpec activationSpec) throws JMSException {
133        ActiveMQConnectionFactory cf = getConnectionFactory();
134        if (cf == null) {
135            cf = createConnectionFactory(getInfo(), activationSpec);
136        }
137        String userName = defaultValue(activationSpec.getUserName(), getInfo().getUserName());
138        String password = defaultValue(activationSpec.getPassword(), getInfo().getPassword());
139        String clientId = activationSpec.getClientId();
140        if (clientId != null) {
141            cf.setClientID(clientId);
142        } else {
143            if (activationSpec.isDurableSubscription()) {
144                log.warn("No clientID specified for durable subscription: " + activationSpec);
145            }
146        }
147        ActiveMQConnection physicalConnection = (ActiveMQConnection) cf.createConnection(userName, password);
148
149        // have we configured a redelivery policy
150        RedeliveryPolicy redeliveryPolicy = activationSpec.redeliveryPolicy();
151        if (redeliveryPolicy != null) {
152            physicalConnection.setRedeliveryPolicy(redeliveryPolicy);
153        }
154        return physicalConnection;
155    }
156
157    /**
158     * @see javax.resource.spi.ResourceAdapter#stop()
159     */
160    @Override
161    public void stop() {
162        log.debug("Stop: " + this.getInfo());
163        started.compareAndSet(true, false);
164        synchronized (endpointWorkers) {
165            while (endpointWorkers.size() > 0) {
166                ActiveMQEndpointActivationKey key = endpointWorkers.keySet().iterator().next();
167                endpointDeactivation(key.getMessageEndpointFactory(), key.getActivationSpec());
168            }
169        }
170
171        synchronized( this ) {
172            if (broker != null) {
173                if( brokerStartThread.isAlive() ) {
174                    brokerStartThread.interrupt();
175                }
176                ServiceSupport.dispose(broker);
177                broker = null;
178            }
179            if (reconnectingXaResource != null) {
180                reconnectingXaResource.stop();
181            }
182        }
183
184        this.bootstrapContext = null;
185        this.reconnectingXaResource = null;
186    }
187
188    /**
189     * @see org.apache.activemq.ra.MessageResourceAdapter#getBootstrapContext()
190     */
191    @Override
192    public BootstrapContext getBootstrapContext() {
193        return bootstrapContext;
194    }
195
196    /**
197     * @see javax.resource.spi.ResourceAdapter#endpointActivation(javax.resource.spi.endpoint.MessageEndpointFactory,
198     *      javax.resource.spi.ActivationSpec)
199     */
200    @Override
201    public void endpointActivation(MessageEndpointFactory endpointFactory, ActivationSpec activationSpec) throws ResourceException {
202
203        // spec section 5.3.3
204        if (!equals(activationSpec.getResourceAdapter())) {
205            throw new ResourceException("Activation spec not initialized with this ResourceAdapter instance (" + activationSpec.getResourceAdapter() + " != " + this + ")");
206        }
207
208        if (!(activationSpec instanceof MessageActivationSpec)) {
209            throw new NotSupportedException("That type of ActivationSpec not supported: " + activationSpec.getClass());
210        }
211
212        ActiveMQEndpointActivationKey key = new ActiveMQEndpointActivationKey(endpointFactory, (MessageActivationSpec)activationSpec);
213        // This is weird.. the same endpoint activated twice.. must be a
214        // container error.
215        if (endpointWorkers.containsKey(key)) {
216            throw new IllegalStateException("Endpoint previously activated");
217        }
218
219        ActiveMQEndpointWorker worker = new ActiveMQEndpointWorker(this, key);
220
221        endpointWorkers.put(key, worker);
222        worker.start();
223    }
224
225    /**
226     * @see javax.resource.spi.ResourceAdapter#endpointDeactivation(javax.resource.spi.endpoint.MessageEndpointFactory,
227     *      javax.resource.spi.ActivationSpec)
228     */
229    @Override
230    public void endpointDeactivation(MessageEndpointFactory endpointFactory, ActivationSpec activationSpec) {
231        if (activationSpec instanceof MessageActivationSpec) {
232            ActiveMQEndpointActivationKey key = new ActiveMQEndpointActivationKey(endpointFactory, (MessageActivationSpec)activationSpec);
233            ActiveMQEndpointWorker worker = null;
234            synchronized (endpointWorkers) {
235                worker = endpointWorkers.remove(key);
236            }
237            if (worker == null) {
238                // This is weird.. that endpoint was not activated.. oh well..
239                // this method
240                // does not throw exceptions so just return.
241                return;
242            }
243            try {
244                worker.stop();
245            } catch (InterruptedException e) {
246                // We interrupted.. we won't throw an exception but will stop
247                // waiting for the worker
248                // to stop.. we tried our best. Keep trying to interrupt the
249                // thread.
250                Thread.currentThread().interrupt();
251            }
252
253        }
254
255    }
256
257    /**
258     * We only connect to one resource manager per ResourceAdapter instance, so
259     * any ActivationSpec will return the same XAResource.
260     *
261     * @see javax.resource.spi.ResourceAdapter#getXAResources(javax.resource.spi.ActivationSpec[])
262     */
263    @Override
264    public XAResource[] getXAResources(ActivationSpec[] activationSpecs) throws ResourceException {
265        LOG.debug("getXAResources: activationSpecs" + (activationSpecs != null ? Arrays.asList(activationSpecs) : "[]") + ", info: " + getInfo());
266        if (!started.get()) {
267            LOG.debug("RAR[" + this.getInfo() + "] stopped or undeployed; no connection available for xa recovery");
268            return new XAResource[]{};
269        }
270        try {
271            synchronized ( this ) {
272                if (reconnectingXaResource == null) {
273                    LOG.debug("Init XAResource with: " + this.getInfo());
274                    reconnectingXaResource = new ReconnectingXAResource(new TransactionContext(makeConnection()));
275                }
276            }
277
278            return new XAResource[]{reconnectingXaResource};
279
280        } catch (Exception e) {
281            throw new ResourceException(e);
282        }
283    }
284
285    private void ensureConnection(TransactionContext xaRecoveryTransactionContext) throws XAException {
286        final ActiveMQConnection existingConnection  = xaRecoveryTransactionContext.getConnection();
287        if (existingConnection == null || existingConnection.isTransportFailed()) {
288            try {
289                LOG.debug("reconnect XAResource with: " + this.getInfo(), existingConnection == null ? "" : existingConnection.getFirstFailureError());
290                xaRecoveryTransactionContext.setConnection(makeConnection());
291            } catch (JMSException e) {
292                throw toXAException(e);
293            } finally {
294                if (existingConnection != null) {
295                    try {
296                        existingConnection.close();
297                    } catch (Exception ignored) {
298                    }
299                }
300            }
301        }
302    }
303
304    private class ReconnectingXAResource implements XAResource {
305        protected TransactionContext delegate;
306
307        ReconnectingXAResource(TransactionContext delegate) {
308            this.delegate = delegate;
309        }
310
311        @Override
312        public void commit(Xid xid, boolean b) throws XAException {
313            ensureConnection(delegate);
314            delegate.commit(xid, b);
315        }
316
317        @Override
318        public void end(Xid xid, int i) throws XAException {
319            ensureConnection(delegate);
320            delegate.end(xid, i);
321        }
322
323        @Override
324        public void forget(Xid xid) throws XAException {
325            ensureConnection(delegate);
326            delegate.forget(xid);
327        }
328
329        @Override
330        public int getTransactionTimeout() throws XAException {
331            ensureConnection(delegate);
332            return delegate.getTransactionTimeout();
333        }
334
335        @Override
336        public boolean isSameRM(XAResource xaResource) throws XAException {
337            if (this == xaResource) {
338                return true;
339            }
340            if (!(xaResource instanceof ReconnectingXAResource)) {
341                return false;
342            }
343
344            ensureConnection(delegate);
345            return delegate.isSameRM(((ReconnectingXAResource)xaResource).delegate);
346        }
347
348        @Override
349        public int prepare(Xid xid) throws XAException {
350            ensureConnection(delegate);
351            return delegate.prepare(xid);
352        }
353
354        @Override
355        public Xid[] recover(int i) throws XAException {
356            ensureConnection(delegate);
357            return delegate.recover(i);
358        }
359
360        @Override
361        public void rollback(Xid xid) throws XAException {
362            ensureConnection(delegate);
363            delegate.rollback(xid);
364
365        }
366
367        @Override
368        public boolean setTransactionTimeout(int i) throws XAException {
369            ensureConnection(delegate);
370            return delegate.setTransactionTimeout(i);
371        }
372
373        @Override
374        public void start(Xid xid, int i) throws XAException {
375            ensureConnection(delegate);
376            delegate.start(xid, i);
377        }
378
379        public void stop() {
380            try {
381                delegate.getConnection().close();
382            } catch (Throwable ignored) {}
383        }
384    };
385
386    // ///////////////////////////////////////////////////////////////////////
387    //
388    // Java Bean getters and setters for this ResourceAdapter class.
389    //
390    // ///////////////////////////////////////////////////////////////////////
391
392    /**
393     * @see org.apache.activemq.ra.MessageResourceAdapter#getBrokerXmlConfig()
394     */
395    @Override
396    public String getBrokerXmlConfig() {
397        return brokerXmlConfig;
398    }
399
400    /**
401     * Sets the <a href="http://activemq.org/Xml+Configuration">XML
402     * configuration file </a> used to configure the ActiveMQ broker via Spring
403     * if using embedded mode.
404     *
405     * @param brokerXmlConfig is the filename which is assumed to be on the
406     *                classpath unless a URL is specified. So a value of
407     *                <code>foo/bar.xml</code> would be assumed to be on the
408     *                classpath whereas <code>file:dir/file.xml</code> would
409     *                use the file system. Any valid URL string is supported.
410     */
411    public void setBrokerXmlConfig(String brokerXmlConfig) {
412        this.brokerXmlConfig = brokerXmlConfig;
413    }
414
415    /**
416     * @see java.lang.Object#equals(java.lang.Object)
417     */
418    @Override
419    public boolean equals(Object o) {
420        if (this == o) {
421            return true;
422        }
423        if (!(o instanceof MessageResourceAdapter)) {
424            return false;
425        }
426
427        final MessageResourceAdapter activeMQResourceAdapter = (MessageResourceAdapter)o;
428
429        if (!getInfo().equals(activeMQResourceAdapter.getInfo())) {
430            return false;
431        }
432        if (notEqual(brokerXmlConfig, activeMQResourceAdapter.getBrokerXmlConfig())) {
433            return false;
434        }
435
436        return true;
437    }
438
439    /**
440     * @see java.lang.Object#hashCode()
441     */
442    @Override
443    public int hashCode() {
444        int result;
445        result = getInfo().hashCode();
446        if (brokerXmlConfig != null) {
447            result ^= brokerXmlConfig.hashCode();
448        }
449        return result;
450    }
451
452    public ActiveMQConnectionFactory getConnectionFactory() {
453        return connectionFactory;
454    }
455
456    public void setConnectionFactory(ActiveMQConnectionFactory aConnectionFactory) {
457        this.connectionFactory = aConnectionFactory;
458    }
459
460
461    }