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 }