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
046/**
047 * Knows how to connect to one ActiveMQ server. It can then activate endpoints
048 * and deliver messages to those end points using the connection configure in
049 * the resource adapter. <p/>Must override equals and hashCode (JCA spec 16.4)
050 *
051 * @org.apache.xbean.XBean element="resourceAdapter" rootElement="true"
052 *                         description="The JCA Resource Adaptor for ActiveMQ"
053 *
054 */
055public class ActiveMQResourceAdapter extends ActiveMQConnectionSupport implements Serializable, MessageResourceAdapter {
056    private static final long serialVersionUID = 360805587169336959L;
057    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQResourceAdapter.class);
058    private transient final HashMap<ActiveMQEndpointActivationKey, ActiveMQEndpointWorker> endpointWorkers = new HashMap<ActiveMQEndpointActivationKey, ActiveMQEndpointWorker>();
059    private final AtomicBoolean started = new AtomicBoolean(false);
060    private transient BootstrapContext bootstrapContext;
061    private String brokerXmlConfig;
062    private transient BrokerService broker;
063    private transient Thread brokerStartThread;
064    private ActiveMQConnectionFactory connectionFactory;
065    private transient TransactionContext xaRecoveryTransactionContext;
066
067    /**
068     *
069     */
070    public ActiveMQResourceAdapter() {
071        super();
072    }
073
074    /**
075     * @see javax.resource.spi.ResourceAdapter#start(javax.resource.spi.BootstrapContext)
076     */
077    @Override
078    public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
079        log.debug("Start: " + this.getInfo());
080        this.bootstrapContext = bootstrapContext;
081        if (brokerXmlConfig != null && brokerXmlConfig.trim().length() > 0) {
082            brokerStartThread = new Thread("Starting ActiveMQ Broker") {
083                @Override
084                public void run () {
085                    try {
086                        // ensure RAR resources are available to xbean (needed for weblogic)
087                        log.debug("original thread context classLoader: " + Thread.currentThread().getContextClassLoader());
088                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
089                        log.debug("current (from getClass()) thread context classLoader: " + Thread.currentThread().getContextClassLoader());
090
091                        synchronized( ActiveMQResourceAdapter.this ) {
092                            broker = BrokerFactory.createBroker(new URI(brokerXmlConfig));
093                        }
094                        broker.start();
095                        // Default the ServerUrl to the local broker if not specified in the ra.xml
096                        if (getServerUrl() == null) {
097                            setServerUrl("vm://" + broker.getBrokerName() + "?create=false");
098                        }
099                    } catch (Throwable e) {
100                        log.warn("Could not start up embeded ActiveMQ Broker '"+brokerXmlConfig+"': "+e.getMessage());
101                        log.debug("Reason for: "+e.getMessage(), e);
102                    }
103                }
104            };
105            brokerStartThread.setDaemon(true);
106            brokerStartThread.start();
107
108            // Wait up to 5 seconds for the broker to start up in the async thread.. otherwise keep going without it..
109            try {
110                brokerStartThread.join(1000*5);
111            } catch (InterruptedException e) {
112                Thread.currentThread().interrupt();
113            }
114        }
115        started.compareAndSet(false, true);
116    }
117
118    public ActiveMQConnection makeConnection() throws JMSException {
119        if( connectionFactory == null ) {
120            return makeConnection(getInfo());
121        } else {
122            return makeConnection(getInfo(), connectionFactory);
123        }
124    }
125
126    /**
127     * @param activationSpec
128     */
129    @Override
130    public ActiveMQConnection makeConnection(MessageActivationSpec activationSpec) throws JMSException {
131        ActiveMQConnectionFactory cf = getConnectionFactory();
132        if (cf == null) {
133            cf = createConnectionFactory(getInfo(), activationSpec);
134        }
135        String userName = defaultValue(activationSpec.getUserName(), getInfo().getUserName());
136        String password = defaultValue(activationSpec.getPassword(), getInfo().getPassword());
137        String clientId = activationSpec.getClientId();
138        if (clientId != null) {
139            cf.setClientID(clientId);
140        } else {
141            if (activationSpec.isDurableSubscription()) {
142                log.warn("No clientID specified for durable subscription: " + activationSpec);
143            }
144        }
145        ActiveMQConnection physicalConnection = (ActiveMQConnection) cf.createConnection(userName, password);
146
147        // have we configured a redelivery policy
148        RedeliveryPolicy redeliveryPolicy = activationSpec.redeliveryPolicy();
149        if (redeliveryPolicy != null) {
150            physicalConnection.setRedeliveryPolicy(redeliveryPolicy);
151        }
152        return physicalConnection;
153    }
154
155    /**
156     * @see javax.resource.spi.ResourceAdapter#stop()
157     */
158    @Override
159    public void stop() {
160        log.debug("Stop: " + this.getInfo());
161        started.compareAndSet(true, false);
162        synchronized (endpointWorkers) {
163            while (endpointWorkers.size() > 0) {
164                ActiveMQEndpointActivationKey key = endpointWorkers.keySet().iterator().next();
165                endpointDeactivation(key.getMessageEndpointFactory(), key.getActivationSpec());
166            }
167        }
168
169        synchronized( this ) {
170            if (broker != null) {
171                if( brokerStartThread.isAlive() ) {
172                    brokerStartThread.interrupt();
173                }
174                ServiceSupport.dispose(broker);
175                broker = null;
176            }
177            if (xaRecoveryTransactionContext != null) {
178                try {
179                    xaRecoveryTransactionContext.getConnection().close();
180                } catch (Throwable ignored) {}
181            }
182        }
183
184        this.bootstrapContext = null;
185        this.xaRecoveryTransactionContext = 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 (xaRecoveryTransactionContext == null) {
273                    LOG.debug("Init XAResource with: " + this.getInfo());
274                    xaRecoveryTransactionContext = new TransactionContext(makeConnection());
275                }
276            }
277            return new XAResource[]{ xaRecoveryTransactionContext };
278        } catch (Exception e) {
279            throw new ResourceException(e);
280        }
281    }
282
283    // ///////////////////////////////////////////////////////////////////////
284    //
285    // Java Bean getters and setters for this ResourceAdapter class.
286    //
287    // ///////////////////////////////////////////////////////////////////////
288
289    /**
290     * @see org.apache.activemq.ra.MessageResourceAdapter#getBrokerXmlConfig()
291     */
292    @Override
293    public String getBrokerXmlConfig() {
294        return brokerXmlConfig;
295    }
296
297    /**
298     * Sets the <a href="http://activemq.org/Xml+Configuration">XML
299     * configuration file </a> used to configure the ActiveMQ broker via Spring
300     * if using embedded mode.
301     *
302     * @param brokerXmlConfig is the filename which is assumed to be on the
303     *                classpath unless a URL is specified. So a value of
304     *                <code>foo/bar.xml</code> would be assumed to be on the
305     *                classpath whereas <code>file:dir/file.xml</code> would
306     *                use the file system. Any valid URL string is supported.
307     */
308    public void setBrokerXmlConfig(String brokerXmlConfig) {
309        this.brokerXmlConfig = brokerXmlConfig;
310    }
311
312    /**
313     * @see java.lang.Object#equals(java.lang.Object)
314     */
315    @Override
316    public boolean equals(Object o) {
317        if (this == o) {
318            return true;
319        }
320        if (!(o instanceof MessageResourceAdapter)) {
321            return false;
322        }
323
324        final MessageResourceAdapter activeMQResourceAdapter = (MessageResourceAdapter)o;
325
326        if (!getInfo().equals(activeMQResourceAdapter.getInfo())) {
327            return false;
328        }
329        if (notEqual(brokerXmlConfig, activeMQResourceAdapter.getBrokerXmlConfig())) {
330            return false;
331        }
332
333        return true;
334    }
335
336    /**
337     * @see java.lang.Object#hashCode()
338     */
339    @Override
340    public int hashCode() {
341        int result;
342        result = getInfo().hashCode();
343        if (brokerXmlConfig != null) {
344            result ^= brokerXmlConfig.hashCode();
345        }
346        return result;
347    }
348
349    public ActiveMQConnectionFactory getConnectionFactory() {
350        return connectionFactory;
351    }
352
353    public void setConnectionFactory(ActiveMQConnectionFactory aConnectionFactory) {
354        this.connectionFactory = aConnectionFactory;
355    }
356
357
358    }