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 }