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.broker.region; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.locks.ReentrantReadWriteLock; 027 028import javax.jms.JMSException; 029import org.apache.activemq.broker.ConnectionContext; 030import org.apache.activemq.broker.ConsumerBrokerExchange; 031import org.apache.activemq.DestinationDoesNotExistException; 032import org.apache.activemq.broker.ProducerBrokerExchange; 033import org.apache.activemq.broker.region.policy.PolicyEntry; 034import org.apache.activemq.broker.region.virtual.CompositeDestinationFilter; 035import org.apache.activemq.command.ActiveMQDestination; 036import org.apache.activemq.command.ConsumerControl; 037import org.apache.activemq.command.ConsumerId; 038import org.apache.activemq.command.ConsumerInfo; 039import org.apache.activemq.command.Message; 040import org.apache.activemq.command.MessageAck; 041import org.apache.activemq.command.MessageDispatchNotification; 042import org.apache.activemq.command.MessagePull; 043import org.apache.activemq.command.ProducerInfo; 044import org.apache.activemq.command.RemoveSubscriptionInfo; 045import org.apache.activemq.command.Response; 046import org.apache.activemq.filter.DestinationFilter; 047import org.apache.activemq.filter.DestinationMap; 048import org.apache.activemq.security.SecurityContext; 049import org.apache.activemq.thread.TaskRunnerFactory; 050import org.apache.activemq.usage.SystemUsage; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * 056 */ 057public abstract class AbstractRegion implements Region { 058 059 private static final Logger LOG = LoggerFactory.getLogger(AbstractRegion.class); 060 061 protected final Map<ActiveMQDestination, Destination> destinations = new ConcurrentHashMap<ActiveMQDestination, Destination>(); 062 protected final DestinationMap destinationMap = new DestinationMap(); 063 protected final Map<ConsumerId, Subscription> subscriptions = new ConcurrentHashMap<ConsumerId, Subscription>(); 064 protected final SystemUsage usageManager; 065 protected final DestinationFactory destinationFactory; 066 protected final DestinationStatistics destinationStatistics; 067 protected final RegionBroker broker; 068 protected boolean autoCreateDestinations = true; 069 protected final TaskRunnerFactory taskRunnerFactory; 070 protected final ReentrantReadWriteLock destinationsLock = new ReentrantReadWriteLock(); 071 protected final Map<ConsumerId, Object> consumerChangeMutexMap = new HashMap<ConsumerId, Object>(); 072 protected boolean started; 073 074 public AbstractRegion(RegionBroker broker, DestinationStatistics destinationStatistics, SystemUsage memoryManager, 075 TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) { 076 if (broker == null) { 077 throw new IllegalArgumentException("null broker"); 078 } 079 this.broker = broker; 080 this.destinationStatistics = destinationStatistics; 081 this.usageManager = memoryManager; 082 this.taskRunnerFactory = taskRunnerFactory; 083 if (destinationFactory == null) { 084 throw new IllegalArgumentException("null destinationFactory"); 085 } 086 this.destinationFactory = destinationFactory; 087 } 088 089 public final void start() throws Exception { 090 started = true; 091 092 Set<ActiveMQDestination> inactiveDests = getInactiveDestinations(); 093 for (Iterator<ActiveMQDestination> iter = inactiveDests.iterator(); iter.hasNext();) { 094 ActiveMQDestination dest = iter.next(); 095 096 ConnectionContext context = new ConnectionContext(); 097 context.setBroker(broker.getBrokerService().getBroker()); 098 context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT); 099 context.getBroker().addDestination(context, dest, false); 100 } 101 destinationsLock.readLock().lock(); 102 try{ 103 for (Iterator<Destination> i = destinations.values().iterator(); i.hasNext();) { 104 Destination dest = i.next(); 105 dest.start(); 106 } 107 } finally { 108 destinationsLock.readLock().unlock(); 109 } 110 } 111 112 public void stop() throws Exception { 113 started = false; 114 destinationsLock.readLock().lock(); 115 try{ 116 for (Iterator<Destination> i = destinations.values().iterator(); i.hasNext();) { 117 Destination dest = i.next(); 118 dest.stop(); 119 } 120 } finally { 121 destinationsLock.readLock().unlock(); 122 } 123 destinations.clear(); 124 } 125 126 public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, 127 boolean createIfTemporary) throws Exception { 128 129 destinationsLock.writeLock().lock(); 130 try { 131 Destination dest = destinations.get(destination); 132 if (dest == null) { 133 if (destination.isTemporary() == false || createIfTemporary) { 134 LOG.debug("{} adding destination: {}", broker.getBrokerName(), destination); 135 dest = createDestination(context, destination); 136 // intercept if there is a valid interceptor defined 137 DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor(); 138 if (destinationInterceptor != null) { 139 dest = destinationInterceptor.intercept(dest); 140 } 141 dest.start(); 142 addSubscriptionsForDestination(context, dest); 143 destinations.put(destination, dest); 144 destinationMap.put(destination, dest); 145 } 146 if (dest == null) { 147 throw new DestinationDoesNotExistException(destination.getQualifiedName()); 148 } 149 } 150 return dest; 151 } finally { 152 destinationsLock.writeLock().unlock(); 153 } 154 } 155 156 public Map<ConsumerId, Subscription> getSubscriptions() { 157 return subscriptions; 158 } 159 160 protected List<Subscription> addSubscriptionsForDestination(ConnectionContext context, Destination dest) 161 throws Exception { 162 163 List<Subscription> rc = new ArrayList<Subscription>(); 164 // Add all consumers that are interested in the destination. 165 for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) { 166 Subscription sub = iter.next(); 167 if (sub.matches(dest.getActiveMQDestination())) { 168 try { 169 dest.addSubscription(context, sub); 170 rc.add(sub); 171 } catch (SecurityException e) { 172 if (sub.isWildcard()) { 173 LOG.debug("Subscription denied for " + sub + " to destination " + 174 dest.getActiveMQDestination() + ": " + e.getMessage()); 175 } else { 176 throw e; 177 } 178 } 179 } 180 } 181 return rc; 182 183 } 184 185 public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) 186 throws Exception { 187 188 // No timeout.. then try to shut down right way, fails if there are 189 // current subscribers. 190 if (timeout == 0) { 191 for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) { 192 Subscription sub = iter.next(); 193 if (sub.matches(destination) ) { 194 throw new JMSException("Destination still has an active subscription: " + destination); 195 } 196 } 197 } 198 199 if (timeout > 0) { 200 // TODO: implement a way to notify the subscribers that we want to 201 // take the down 202 // the destination and that they should un-subscribe.. Then wait up 203 // to timeout time before 204 // dropping the subscription. 205 } 206 207 LOG.debug("{} removing destination: {}", broker.getBrokerName(), destination); 208 209 destinationsLock.writeLock().lock(); 210 try { 211 Destination dest = destinations.remove(destination); 212 if (dest != null) { 213 // timeout<0 or we timed out, we now force any remaining 214 // subscriptions to un-subscribe. 215 for (Iterator<Subscription> iter = subscriptions.values().iterator(); iter.hasNext();) { 216 Subscription sub = iter.next(); 217 if (sub.matches(destination)) { 218 dest.removeSubscription(context, sub, 0l); 219 } 220 } 221 destinationMap.remove(destination, dest); 222 dispose(context, dest); 223 DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor(); 224 if (destinationInterceptor != null) { 225 destinationInterceptor.remove(dest); 226 } 227 228 } else { 229 LOG.debug("Cannot remove a destination that doesn't exist: {}", destination); 230 } 231 } finally { 232 destinationsLock.writeLock().unlock(); 233 } 234 } 235 236 /** 237 * Provide an exact or wildcard lookup of destinations in the region 238 * 239 * @return a set of matching destination objects. 240 */ 241 @SuppressWarnings("unchecked") 242 public Set<Destination> getDestinations(ActiveMQDestination destination) { 243 destinationsLock.readLock().lock(); 244 try{ 245 return destinationMap.get(destination); 246 } finally { 247 destinationsLock.readLock().unlock(); 248 } 249 } 250 251 public Map<ActiveMQDestination, Destination> getDestinationMap() { 252 return destinations; 253 } 254 255 @SuppressWarnings("unchecked") 256 public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { 257 LOG.debug("{} adding consumer: {} for destination: {}", new Object[]{ broker.getBrokerName(), info.getConsumerId(), info.getDestination() }); 258 ActiveMQDestination destination = info.getDestination(); 259 if (destination != null && !destination.isPattern() && !destination.isComposite()) { 260 // lets auto-create the destination 261 lookup(context, destination,true); 262 } 263 264 Object addGuard; 265 synchronized (consumerChangeMutexMap) { 266 addGuard = consumerChangeMutexMap.get(info.getConsumerId()); 267 if (addGuard == null) { 268 addGuard = new Object(); 269 consumerChangeMutexMap.put(info.getConsumerId(), addGuard); 270 } 271 } 272 synchronized (addGuard) { 273 Subscription o = subscriptions.get(info.getConsumerId()); 274 if (o != null) { 275 LOG.warn("A duplicate subscription was detected. Clients may be misbehaving. Later warnings you may see about subscription removal are a consequence of this."); 276 return o; 277 } 278 279 // We may need to add some destinations that are in persistent store 280 // but not active 281 // in the broker. 282 // 283 // TODO: think about this a little more. This is good cause 284 // destinations are not loaded into 285 // memory until a client needs to use the queue, but a management 286 // agent viewing the 287 // broker will not see a destination that exists in persistent 288 // store. We may want to 289 // eagerly load all destinations into the broker but have an 290 // inactive state for the 291 // destination which has reduced memory usage. 292 // 293 DestinationFilter.parseFilter(info.getDestination()); 294 295 Subscription sub = createSubscription(context, info); 296 297 // At this point we're done directly manipulating subscriptions, 298 // but we need to retain the synchronized block here. Consider 299 // otherwise what would happen if at this point a second 300 // thread added, then removed, as would be allowed with 301 // no mutex held. Remove is only essentially run once 302 // so everything after this point would be leaked. 303 304 // Add the subscription to all the matching queues. 305 // But copy the matches first - to prevent deadlocks 306 List<Destination> addList = new ArrayList<Destination>(); 307 destinationsLock.readLock().lock(); 308 try { 309 for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) { 310 addList.add(dest); 311 } 312 // ensure sub visible to any new dest addSubscriptionsForDestination 313 subscriptions.put(info.getConsumerId(), sub); 314 } finally { 315 destinationsLock.readLock().unlock(); 316 } 317 318 List<Destination> removeList = new ArrayList<Destination>(); 319 for (Destination dest : addList) { 320 try { 321 dest.addSubscription(context, sub); 322 removeList.add(dest); 323 } catch (SecurityException e){ 324 if (sub.isWildcard()) { 325 LOG.debug("Subscription denied for " + sub + " to destination " + 326 dest.getActiveMQDestination() + ": " + e.getMessage()); 327 } else { 328 // remove partial subscriptions 329 for (Destination remove : removeList) { 330 try { 331 remove.removeSubscription(context, sub, info.getLastDeliveredSequenceId()); 332 } catch (Exception ex) { 333 LOG.error("Error unsubscribing " + sub + " from " + remove + ": " + ex.getMessage(), ex); 334 } 335 } 336 subscriptions.remove(info.getConsumerId()); 337 removeList.clear(); 338 throw e; 339 } 340 } 341 } 342 removeList.clear(); 343 344 if (info.isBrowser()) { 345 ((QueueBrowserSubscription) sub).destinationsAdded(); 346 } 347 348 return sub; 349 } 350 } 351 352 /** 353 * Get all the Destinations that are in storage 354 * 355 * @return Set of all stored destinations 356 */ 357 @SuppressWarnings("rawtypes") 358 public Set getDurableDestinations() { 359 return destinationFactory.getDestinations(); 360 } 361 362 /** 363 * @return all Destinations that don't have active consumers 364 */ 365 protected Set<ActiveMQDestination> getInactiveDestinations() { 366 Set<ActiveMQDestination> inactiveDests = destinationFactory.getDestinations(); 367 destinationsLock.readLock().lock(); 368 try { 369 inactiveDests.removeAll(destinations.keySet()); 370 } finally { 371 destinationsLock.readLock().unlock(); 372 } 373 return inactiveDests; 374 } 375 376 @SuppressWarnings("unchecked") 377 public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception { 378 LOG.debug("{} removing consumer: {} for destination: {}", new Object[]{ broker.getBrokerName(), info.getConsumerId(), info.getDestination() }); 379 380 Subscription sub = subscriptions.remove(info.getConsumerId()); 381 // The sub could be removed elsewhere - see ConnectionSplitBroker 382 if (sub != null) { 383 384 // remove the subscription from all the matching queues. 385 List<Destination> removeList = new ArrayList<Destination>(); 386 destinationsLock.readLock().lock(); 387 try { 388 for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) { 389 removeList.add(dest); 390 } 391 } finally { 392 destinationsLock.readLock().unlock(); 393 } 394 for (Destination dest : removeList) { 395 dest.removeSubscription(context, sub, info.getLastDeliveredSequenceId()); 396 } 397 398 destroySubscription(sub); 399 } 400 synchronized (consumerChangeMutexMap) { 401 consumerChangeMutexMap.remove(info.getConsumerId()); 402 } 403 } 404 405 protected void destroySubscription(Subscription sub) { 406 sub.destroy(); 407 } 408 409 public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception { 410 throw new JMSException("Invalid operation."); 411 } 412 413 public void send(final ProducerBrokerExchange producerExchange, Message messageSend) throws Exception { 414 final ConnectionContext context = producerExchange.getConnectionContext(); 415 416 if (producerExchange.isMutable() || producerExchange.getRegionDestination() == null) { 417 final Destination regionDestination = lookup(context, messageSend.getDestination(),false); 418 producerExchange.setRegionDestination(regionDestination); 419 } 420 421 producerExchange.getRegionDestination().send(producerExchange, messageSend); 422 423 if (producerExchange.getProducerState() != null && producerExchange.getProducerState().getInfo() != null){ 424 producerExchange.getProducerState().getInfo().incrementSentCount(); 425 } 426 } 427 428 public void acknowledge(ConsumerBrokerExchange consumerExchange, MessageAck ack) throws Exception { 429 Subscription sub = consumerExchange.getSubscription(); 430 if (sub == null) { 431 sub = subscriptions.get(ack.getConsumerId()); 432 if (sub == null) { 433 if (!consumerExchange.getConnectionContext().isInRecoveryMode()) { 434 LOG.warn("Ack for non existent subscription, ack: {}", ack); 435 throw new IllegalArgumentException("The subscription does not exist: " + ack.getConsumerId()); 436 } else { 437 LOG.debug("Ack for non existent subscription in recovery, ack: {}", ack); 438 return; 439 } 440 } 441 consumerExchange.setSubscription(sub); 442 } 443 sub.acknowledge(consumerExchange.getConnectionContext(), ack); 444 } 445 446 public Response messagePull(ConnectionContext context, MessagePull pull) throws Exception { 447 Subscription sub = subscriptions.get(pull.getConsumerId()); 448 if (sub == null) { 449 throw new IllegalArgumentException("The subscription does not exist: " + pull.getConsumerId()); 450 } 451 return sub.pullMessage(context, pull); 452 } 453 454 protected Destination lookup(ConnectionContext context, ActiveMQDestination destination,boolean createTemporary) throws Exception { 455 Destination dest = null; 456 457 destinationsLock.readLock().lock(); 458 try { 459 dest = destinations.get(destination); 460 } finally { 461 destinationsLock.readLock().unlock(); 462 } 463 464 if (dest == null) { 465 if (isAutoCreateDestinations()) { 466 // Try to auto create the destination... re-invoke broker 467 // from the 468 // top so that the proper security checks are performed. 469 context.getBroker().addDestination(context, destination, createTemporary); 470 dest = addDestination(context, destination, false); 471 // We should now have the dest created. 472 destinationsLock.readLock().lock(); 473 try { 474 dest = destinations.get(destination); 475 } finally { 476 destinationsLock.readLock().unlock(); 477 } 478 } 479 480 if (dest == null) { 481 throw new JMSException("The destination " + destination + " does not exist."); 482 } 483 } 484 return dest; 485 } 486 487 public void processDispatchNotification(MessageDispatchNotification messageDispatchNotification) throws Exception { 488 Subscription sub = subscriptions.get(messageDispatchNotification.getConsumerId()); 489 if (sub != null) { 490 sub.processMessageDispatchNotification(messageDispatchNotification); 491 } else { 492 throw new JMSException("Slave broker out of sync with master - Subscription: " 493 + messageDispatchNotification.getConsumerId() + " on " 494 + messageDispatchNotification.getDestination() + " does not exist for dispatch of message: " 495 + messageDispatchNotification.getMessageId()); 496 } 497 } 498 499 /* 500 * For a Queue/TempQueue, dispatch order is imperative to match acks, so the 501 * dispatch is deferred till the notification to ensure that the 502 * subscription chosen by the master is used. AMQ-2102 503 */ 504 protected void processDispatchNotificationViaDestination(MessageDispatchNotification messageDispatchNotification) 505 throws Exception { 506 Destination dest = null; 507 destinationsLock.readLock().lock(); 508 try { 509 dest = destinations.get(messageDispatchNotification.getDestination()); 510 } finally { 511 destinationsLock.readLock().unlock(); 512 } 513 514 if (dest != null) { 515 dest.processDispatchNotification(messageDispatchNotification); 516 } else { 517 throw new JMSException("Slave broker out of sync with master - Destination: " 518 + messageDispatchNotification.getDestination() + " does not exist for consumer " 519 + messageDispatchNotification.getConsumerId() + " with message: " 520 + messageDispatchNotification.getMessageId()); 521 } 522 } 523 524 public void gc() { 525 for (Subscription sub : subscriptions.values()) { 526 sub.gc(); 527 } 528 529 destinationsLock.readLock().lock(); 530 try { 531 for (Destination dest : destinations.values()) { 532 dest.gc(); 533 } 534 } finally { 535 destinationsLock.readLock().unlock(); 536 } 537 } 538 539 protected abstract Subscription createSubscription(ConnectionContext context, ConsumerInfo info) throws Exception; 540 541 protected Destination createDestination(ConnectionContext context, ActiveMQDestination destination) 542 throws Exception { 543 return destinationFactory.createDestination(context, destination, destinationStatistics); 544 } 545 546 public boolean isAutoCreateDestinations() { 547 return autoCreateDestinations; 548 } 549 550 public void setAutoCreateDestinations(boolean autoCreateDestinations) { 551 this.autoCreateDestinations = autoCreateDestinations; 552 } 553 554 @SuppressWarnings("unchecked") 555 public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception { 556 destinationsLock.readLock().lock(); 557 try { 558 for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) { 559 dest.addProducer(context, info); 560 } 561 } finally { 562 destinationsLock.readLock().unlock(); 563 } 564 } 565 566 /** 567 * Removes a Producer. 568 * 569 * @param context 570 * the environment the operation is being executed under. 571 * @throws Exception 572 * TODO 573 */ 574 @SuppressWarnings("unchecked") 575 public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception { 576 destinationsLock.readLock().lock(); 577 try { 578 for (Destination dest : (Set<Destination>) destinationMap.get(info.getDestination())) { 579 dest.removeProducer(context, info); 580 } 581 } finally { 582 destinationsLock.readLock().unlock(); 583 } 584 } 585 586 protected void dispose(ConnectionContext context, Destination dest) throws Exception { 587 dest.dispose(context); 588 dest.stop(); 589 destinationFactory.removeDestination(dest); 590 } 591 592 public void processConsumerControl(ConsumerBrokerExchange consumerExchange, ConsumerControl control) { 593 Subscription sub = subscriptions.get(control.getConsumerId()); 594 if (sub != null && sub instanceof AbstractSubscription) { 595 ((AbstractSubscription) sub).setPrefetchSize(control.getPrefetch()); 596 if (broker.getDestinationPolicy() != null) { 597 PolicyEntry entry = broker.getDestinationPolicy().getEntryFor(control.getDestination()); 598 if (entry != null) { 599 entry.configurePrefetch(sub); 600 } 601 } 602 LOG.debug("setting prefetch: {}, on subscription: {}; resulting value: {}", new Object[]{ control.getPrefetch(), control.getConsumerId(), sub.getConsumerInfo().getPrefetchSize()}); 603 try { 604 lookup(consumerExchange.getConnectionContext(), control.getDestination(),false).wakeup(); 605 } catch (Exception e) { 606 LOG.warn("failed to deliver post consumerControl dispatch-wakeup, to destination: {}", control.getDestination(), e); 607 } 608 } 609 } 610 611 public void reapplyInterceptor() { 612 destinationsLock.writeLock().lock(); 613 try { 614 DestinationInterceptor destinationInterceptor = broker.getDestinationInterceptor(); 615 Map<ActiveMQDestination, Destination> map = getDestinationMap(); 616 for (ActiveMQDestination key : map.keySet()) { 617 Destination destination = map.get(key); 618 if (destination instanceof CompositeDestinationFilter) { 619 destination = ((CompositeDestinationFilter) destination).next; 620 } 621 if (destinationInterceptor != null) { 622 destination = destinationInterceptor.intercept(destination); 623 } 624 getDestinationMap().put(key, destination); 625 destinations.put(key, destination); 626 } 627 } finally { 628 destinationsLock.writeLock().unlock(); 629 } 630 } 631}