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.advisory;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028import java.util.concurrent.locks.ReentrantReadWriteLock;
029
030import org.apache.activemq.broker.Broker;
031import org.apache.activemq.broker.BrokerFilter;
032import org.apache.activemq.broker.BrokerService;
033import org.apache.activemq.broker.ConnectionContext;
034import org.apache.activemq.broker.ProducerBrokerExchange;
035import org.apache.activemq.broker.region.BaseDestination;
036import org.apache.activemq.broker.region.Destination;
037import org.apache.activemq.broker.region.DurableTopicSubscription;
038import org.apache.activemq.broker.region.MessageReference;
039import org.apache.activemq.broker.region.RegionBroker;
040import org.apache.activemq.broker.region.Subscription;
041import org.apache.activemq.broker.region.TopicRegion;
042import org.apache.activemq.broker.region.TopicSubscription;
043import org.apache.activemq.broker.region.virtual.VirtualDestination;
044import org.apache.activemq.command.ActiveMQDestination;
045import org.apache.activemq.command.ActiveMQMessage;
046import org.apache.activemq.command.ActiveMQTopic;
047import org.apache.activemq.command.BrokerInfo;
048import org.apache.activemq.command.Command;
049import org.apache.activemq.command.ConnectionId;
050import org.apache.activemq.command.ConnectionInfo;
051import org.apache.activemq.command.ConsumerId;
052import org.apache.activemq.command.ConsumerInfo;
053import org.apache.activemq.command.DestinationInfo;
054import org.apache.activemq.command.Message;
055import org.apache.activemq.command.MessageId;
056import org.apache.activemq.command.ProducerId;
057import org.apache.activemq.command.ProducerInfo;
058import org.apache.activemq.command.RemoveSubscriptionInfo;
059import org.apache.activemq.command.SessionId;
060import org.apache.activemq.security.SecurityContext;
061import org.apache.activemq.state.ProducerState;
062import org.apache.activemq.usage.Usage;
063import org.apache.activemq.util.IdGenerator;
064import org.apache.activemq.util.LongSequenceGenerator;
065import org.apache.activemq.util.SubscriptionKey;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069/**
070 * This broker filter handles tracking the state of the broker for purposes of
071 * publishing advisory messages to advisory consumers.
072 */
073public class AdvisoryBroker extends BrokerFilter {
074
075    private static final Logger LOG = LoggerFactory.getLogger(AdvisoryBroker.class);
076    private static final IdGenerator ID_GENERATOR = new IdGenerator();
077
078    protected final ConcurrentMap<ConnectionId, ConnectionInfo> connections = new ConcurrentHashMap<ConnectionId, ConnectionInfo>();
079
080    private final ReentrantReadWriteLock consumersLock = new ReentrantReadWriteLock();
081    protected final Map<ConsumerId, ConsumerInfo> consumers = new LinkedHashMap<ConsumerId, ConsumerInfo>();
082
083    /**
084     * This is a set to track all of the virtual destinations that have been added to the broker so
085     * they can be easily referenced later.
086     */
087    protected final Set<VirtualDestination> virtualDestinations = Collections.newSetFromMap(new ConcurrentHashMap<VirtualDestination, Boolean>());
088    /**
089     * This is a map to track all consumers that exist on the virtual destination so that we can fire
090     * an advisory later when they go away to remove the demand.
091     */
092    protected final ConcurrentMap<ConsumerInfo, VirtualDestination> virtualDestinationConsumers = new ConcurrentHashMap<>();
093    /**
094     * This is a map to track unique demand for the existence of a virtual destination so we make sure
095     * we don't send duplicate advisories.
096     */
097    protected final ConcurrentMap<VirtualConsumerPair, ConsumerInfo> brokerConsumerDests = new ConcurrentHashMap<>();
098
099    protected final ConcurrentMap<ProducerId, ProducerInfo> producers = new ConcurrentHashMap<ProducerId, ProducerInfo>();
100    protected final ConcurrentMap<ActiveMQDestination, DestinationInfo> destinations = new ConcurrentHashMap<ActiveMQDestination, DestinationInfo>();
101    protected final ConcurrentMap<BrokerInfo, ActiveMQMessage> networkBridges = new ConcurrentHashMap<BrokerInfo, ActiveMQMessage>();
102    protected final ProducerId advisoryProducerId = new ProducerId();
103
104    private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator();
105
106    private VirtualDestinationMatcher virtualDestinationMatcher = new DestinationFilterVirtualDestinationMatcher();
107
108    public AdvisoryBroker(Broker next) {
109        super(next);
110        advisoryProducerId.setConnectionId(ID_GENERATOR.generateId());
111    }
112
113    @Override
114    public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
115        super.addConnection(context, info);
116
117        ActiveMQTopic topic = AdvisorySupport.getConnectionAdvisoryTopic();
118        // do not distribute passwords in advisory messages. usernames okay
119        ConnectionInfo copy = info.copy();
120        copy.setPassword("");
121        fireAdvisory(context, topic, copy);
122        connections.put(copy.getConnectionId(), copy);
123    }
124
125    @Override
126    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
127        Subscription answer = super.addConsumer(context, info);
128
129        // Don't advise advisory topics.
130        if (!AdvisorySupport.isAdvisoryTopic(info.getDestination())) {
131            ActiveMQTopic topic = AdvisorySupport.getConsumerAdvisoryTopic(info.getDestination());
132            consumersLock.writeLock().lock();
133            try {
134                consumers.put(info.getConsumerId(), info);
135
136                //check if this is a consumer on a destination that matches a virtual destination
137                if (getBrokerService().isUseVirtualDestSubs()) {
138                    for (VirtualDestination virtualDestination : virtualDestinations) {
139                        if (virtualDestinationMatcher.matches(virtualDestination, info.getDestination())) {
140                            fireVirtualDestinationAddAdvisory(context, info, info.getDestination(), virtualDestination);
141                        }
142                    }
143                }
144            } finally {
145                consumersLock.writeLock().unlock();
146            }
147            fireConsumerAdvisory(context, info.getDestination(), topic, info);
148        } else {
149            // We need to replay all the previously collected state objects
150            // for this newly added consumer.
151            if (AdvisorySupport.isConnectionAdvisoryTopic(info.getDestination())) {
152                // Replay the connections.
153                for (Iterator<ConnectionInfo> iter = connections.values().iterator(); iter.hasNext(); ) {
154                    ConnectionInfo value = iter.next();
155                    ActiveMQTopic topic = AdvisorySupport.getConnectionAdvisoryTopic();
156                    fireAdvisory(context, topic, value, info.getConsumerId());
157                }
158            }
159
160            // We check here whether the Destination is Temporary Destination specific or not since we
161            // can avoid sending advisory messages to the consumer if it only wants Temporary Destination
162            // notifications.  If its not just temporary destination related destinations then we have
163            // to send them all, a composite destination could want both.
164            if (AdvisorySupport.isTempDestinationAdvisoryTopic(info.getDestination())) {
165                // Replay the temporary destinations.
166                for (DestinationInfo destination : destinations.values()) {
167                    if (destination.getDestination().isTemporary()) {
168                        ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destination.getDestination());
169                        fireAdvisory(context, topic, destination, info.getConsumerId());
170                    }
171                }
172            } else if (AdvisorySupport.isDestinationAdvisoryTopic(info.getDestination())) {
173                // Replay all the destinations.
174                for (DestinationInfo destination : destinations.values()) {
175                    ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destination.getDestination());
176                    fireAdvisory(context, topic, destination, info.getConsumerId());
177                }
178            }
179
180            // Replay the producers.
181            if (AdvisorySupport.isProducerAdvisoryTopic(info.getDestination())) {
182                for (Iterator<ProducerInfo> iter = producers.values().iterator(); iter.hasNext(); ) {
183                    ProducerInfo value = iter.next();
184                    ActiveMQTopic topic = AdvisorySupport.getProducerAdvisoryTopic(value.getDestination());
185                    fireProducerAdvisory(context, value.getDestination(), topic, value, info.getConsumerId());
186                }
187            }
188
189            // Replay the consumers.
190            if (AdvisorySupport.isConsumerAdvisoryTopic(info.getDestination())) {
191                consumersLock.readLock().lock();
192                try {
193                    for (Iterator<ConsumerInfo> iter = consumers.values().iterator(); iter.hasNext(); ) {
194                        ConsumerInfo value = iter.next();
195                        ActiveMQTopic topic = AdvisorySupport.getConsumerAdvisoryTopic(value.getDestination());
196                        fireConsumerAdvisory(context, value.getDestination(), topic, value, info.getConsumerId());
197                    }
198                } finally {
199                    consumersLock.readLock().unlock();
200                }
201            }
202
203            // Replay the virtual destination consumers.
204            if (AdvisorySupport.isVirtualDestinationConsumerAdvisoryTopic(info.getDestination())) {
205                for (Iterator<ConsumerInfo> iter = virtualDestinationConsumers.keySet().iterator(); iter.hasNext(); ) {
206                    ConsumerInfo key = iter.next();
207                    ActiveMQTopic topic = AdvisorySupport.getVirtualDestinationConsumerAdvisoryTopic(key.getDestination());
208                    fireConsumerAdvisory(context, key.getDestination(), topic, key);
209              }
210            }
211
212            // Replay network bridges
213            if (AdvisorySupport.isNetworkBridgeAdvisoryTopic(info.getDestination())) {
214                for (Iterator<BrokerInfo> iter = networkBridges.keySet().iterator(); iter.hasNext(); ) {
215                    BrokerInfo key = iter.next();
216                    ActiveMQTopic topic = AdvisorySupport.getNetworkBridgeAdvisoryTopic();
217                    fireAdvisory(context, topic, key, null, networkBridges.get(key));
218                }
219            }
220        }
221        return answer;
222    }
223
224    @Override
225    public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
226        super.addProducer(context, info);
227
228        // Don't advise advisory topics.
229        if (info.getDestination() != null && !AdvisorySupport.isAdvisoryTopic(info.getDestination())) {
230            ActiveMQTopic topic = AdvisorySupport.getProducerAdvisoryTopic(info.getDestination());
231            fireProducerAdvisory(context, info.getDestination(), topic, info);
232            producers.put(info.getProducerId(), info);
233        }
234    }
235
236    @Override
237    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, boolean create) throws Exception {
238        Destination answer = super.addDestination(context, destination, create);
239        if (!AdvisorySupport.isAdvisoryTopic(destination)) {
240            //for queues, create demand if isUseVirtualDestSubsOnCreation is true
241            if (getBrokerService().isUseVirtualDestSubsOnCreation() && destination.isQueue()) {
242                //check if this new destination matches a virtual destination that exists
243                for (VirtualDestination virtualDestination : virtualDestinations) {
244                    if (virtualDestinationMatcher.matches(virtualDestination, destination)) {
245                        fireVirtualDestinationAddAdvisory(context, null, destination, virtualDestination);
246                    }
247                }
248            }
249
250            DestinationInfo info = new DestinationInfo(context.getConnectionId(), DestinationInfo.ADD_OPERATION_TYPE, destination);
251            DestinationInfo previous = destinations.putIfAbsent(destination, info);
252            if (previous == null) {
253                ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destination);
254                fireAdvisory(context, topic, info);
255            }
256        }
257        return answer;
258    }
259
260    @Override
261    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
262        ActiveMQDestination destination = info.getDestination();
263        next.addDestinationInfo(context, info);
264
265        if (!AdvisorySupport.isAdvisoryTopic(destination)) {
266            DestinationInfo previous = destinations.putIfAbsent(destination, info);
267            if (previous == null) {
268                ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destination);
269                fireAdvisory(context, topic, info);
270            }
271        }
272    }
273
274    @Override
275    public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception {
276        super.removeDestination(context, destination, timeout);
277        DestinationInfo info = destinations.remove(destination);
278        if (info != null) {
279
280            //on destination removal, remove all demand if using virtual dest subs
281            if (getBrokerService().isUseVirtualDestSubs()) {
282                for (ConsumerInfo consumerInfo : virtualDestinationConsumers.keySet()) {
283                    //find all consumers for this virtual destination
284                    VirtualDestination virtualDestination = virtualDestinationConsumers.get(consumerInfo);
285
286                    //find a consumer that matches this virtualDest and destination
287                    if (virtualDestinationMatcher.matches(virtualDestination, destination)) {
288                        //in case of multiple matches
289                        VirtualConsumerPair key = new VirtualConsumerPair(virtualDestination, destination);
290                        ConsumerInfo i = brokerConsumerDests.get(key);
291                        if (consumerInfo.equals(i)) {
292                            if (brokerConsumerDests.remove(key) != null) {
293                                fireVirtualDestinationRemoveAdvisory(context, consumerInfo);
294                                break;
295                            }
296                        }
297                    }
298                }
299            }
300
301            // ensure we don't modify (and loose/overwrite) an in-flight add advisory, so duplicate
302            info = info.copy();
303            info.setDestination(destination);
304            info.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
305            ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destination);
306            fireAdvisory(context, topic, info);
307            ActiveMQTopic[] advisoryDestinations = AdvisorySupport.getAllDestinationAdvisoryTopics(destination);
308            for (ActiveMQTopic advisoryDestination : advisoryDestinations) {
309                try {
310                    next.removeDestination(context, advisoryDestination, -1);
311                } catch (Exception expectedIfDestinationDidNotExistYet) {
312                }
313            }
314        }
315    }
316
317    @Override
318    public void removeDestinationInfo(ConnectionContext context, DestinationInfo destInfo) throws Exception {
319        super.removeDestinationInfo(context, destInfo);
320        DestinationInfo info = destinations.remove(destInfo.getDestination());
321        if (info != null) {
322            // ensure we don't modify (and loose/overwrite) an in-flight add advisory, so duplicate
323            info = info.copy();
324            info.setDestination(destInfo.getDestination());
325            info.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
326            ActiveMQTopic topic = AdvisorySupport.getDestinationAdvisoryTopic(destInfo.getDestination());
327            fireAdvisory(context, topic, info);
328            ActiveMQTopic[] advisoryDestinations = AdvisorySupport.getAllDestinationAdvisoryTopics(destInfo.getDestination());
329            for (ActiveMQTopic advisoryDestination : advisoryDestinations) {
330                try {
331                    next.removeDestination(context, advisoryDestination, -1);
332                } catch (Exception expectedIfDestinationDidNotExistYet) {
333                }
334            }
335        }
336    }
337
338    @Override
339    public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
340        super.removeConnection(context, info, error);
341
342        ActiveMQTopic topic = AdvisorySupport.getConnectionAdvisoryTopic();
343        fireAdvisory(context, topic, info.createRemoveCommand());
344        connections.remove(info.getConnectionId());
345    }
346
347    @Override
348    public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
349        super.removeConsumer(context, info);
350
351        // Don't advise advisory topics.
352        ActiveMQDestination dest = info.getDestination();
353        if (!AdvisorySupport.isAdvisoryTopic(dest)) {
354            ActiveMQTopic topic = AdvisorySupport.getConsumerAdvisoryTopic(dest);
355            consumersLock.writeLock().lock();
356            try {
357                consumers.remove(info.getConsumerId());
358
359                //remove the demand for this consumer if it matches a virtual destination
360                if(getBrokerService().isUseVirtualDestSubs()) {
361                    fireVirtualDestinationRemoveAdvisory(context, info);
362                }
363            } finally {
364                consumersLock.writeLock().unlock();
365            }
366            if (!dest.isTemporary() || destinations.containsKey(dest)) {
367                fireConsumerAdvisory(context, dest, topic, info.createRemoveCommand());
368            }
369        }
370    }
371
372    @Override
373    public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception {
374        SubscriptionKey key = new SubscriptionKey(context.getClientId(), info.getSubscriptionName());
375
376        RegionBroker regionBroker = null;
377        if (next instanceof RegionBroker) {
378            regionBroker = (RegionBroker) next;
379        } else {
380            BrokerService service = next.getBrokerService();
381            regionBroker = (RegionBroker) service.getRegionBroker();
382        }
383
384        if (regionBroker == null) {
385            LOG.warn("Cannot locate a RegionBroker instance to pass along the removeSubscription call");
386            throw new IllegalStateException("No RegionBroker found.");
387        }
388
389        DurableTopicSubscription sub = ((TopicRegion) regionBroker.getTopicRegion()).getDurableSubscription(key);
390
391        super.removeSubscription(context, info);
392
393        if (sub == null) {
394            LOG.warn("We cannot send an advisory message for a durable sub removal when we don't know about the durable sub");
395            return;
396        }
397
398        ActiveMQDestination dest = sub.getConsumerInfo().getDestination();
399
400        // Don't advise advisory topics.
401        if (!AdvisorySupport.isAdvisoryTopic(dest)) {
402            ActiveMQTopic topic = AdvisorySupport.getConsumerAdvisoryTopic(dest);
403            fireConsumerAdvisory(context, dest, topic, info);
404        }
405
406    }
407
408    @Override
409    public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception {
410        super.removeProducer(context, info);
411
412        // Don't advise advisory topics.
413        ActiveMQDestination dest = info.getDestination();
414        if (info.getDestination() != null && !AdvisorySupport.isAdvisoryTopic(dest)) {
415            ActiveMQTopic topic = AdvisorySupport.getProducerAdvisoryTopic(dest);
416            producers.remove(info.getProducerId());
417            if (!dest.isTemporary() || destinations.containsKey(dest)) {
418                fireProducerAdvisory(context, dest, topic, info.createRemoveCommand());
419            }
420        }
421    }
422
423    @Override
424    public void messageExpired(ConnectionContext context, MessageReference messageReference, Subscription subscription) {
425        super.messageExpired(context, messageReference, subscription);
426        try {
427            if (!messageReference.isAdvisory()) {
428                BaseDestination baseDestination = (BaseDestination) messageReference.getMessage().getRegionDestination();
429                ActiveMQTopic topic = AdvisorySupport.getExpiredMessageTopic(baseDestination.getActiveMQDestination());
430                Message payload = messageReference.getMessage().copy();
431                payload.clearBody();
432                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
433                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_MESSAGE_ID, payload.getMessageId().toString());
434                fireAdvisory(context, topic, payload, null, advisoryMessage);
435            }
436        } catch (Exception e) {
437            handleFireFailure("expired", e);
438        }
439    }
440
441    @Override
442    public void messageConsumed(ConnectionContext context, MessageReference messageReference) {
443        super.messageConsumed(context, messageReference);
444        try {
445            if (!messageReference.isAdvisory()) {
446                BaseDestination baseDestination = (BaseDestination) messageReference.getMessage().getRegionDestination();
447                ActiveMQTopic topic = AdvisorySupport.getMessageConsumedAdvisoryTopic(baseDestination.getActiveMQDestination());
448                Message payload = messageReference.getMessage().copy();
449                payload.clearBody();
450                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
451                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_MESSAGE_ID, payload.getMessageId().toString());
452                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_DESTINATION, baseDestination.getActiveMQDestination().getQualifiedName());
453                fireAdvisory(context, topic, payload, null, advisoryMessage);
454            }
455        } catch (Exception e) {
456            handleFireFailure("consumed", e);
457        }
458    }
459
460    @Override
461    public void messageDelivered(ConnectionContext context, MessageReference messageReference) {
462        super.messageDelivered(context, messageReference);
463        try {
464            if (!messageReference.isAdvisory()) {
465                BaseDestination baseDestination = (BaseDestination) messageReference.getMessage().getRegionDestination();
466                ActiveMQTopic topic = AdvisorySupport.getMessageDeliveredAdvisoryTopic(baseDestination.getActiveMQDestination());
467                Message payload = messageReference.getMessage().copy();
468                payload.clearBody();
469                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
470                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_MESSAGE_ID, payload.getMessageId().toString());
471                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_DESTINATION, baseDestination.getActiveMQDestination().getQualifiedName());
472                fireAdvisory(context, topic, payload, null, advisoryMessage);
473            }
474        } catch (Exception e) {
475            handleFireFailure("delivered", e);
476        }
477    }
478
479    @Override
480    public void messageDiscarded(ConnectionContext context, Subscription sub, MessageReference messageReference) {
481        super.messageDiscarded(context, sub, messageReference);
482        try {
483            if (!messageReference.isAdvisory()) {
484                BaseDestination baseDestination = (BaseDestination) messageReference.getMessage().getRegionDestination();
485                ActiveMQTopic topic = AdvisorySupport.getMessageDiscardedAdvisoryTopic(baseDestination.getActiveMQDestination());
486                Message payload = messageReference.getMessage().copy();
487                payload.clearBody();
488                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
489                if (sub instanceof TopicSubscription) {
490                    advisoryMessage.setIntProperty(AdvisorySupport.MSG_PROPERTY_DISCARDED_COUNT, ((TopicSubscription) sub).discarded());
491                }
492                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_MESSAGE_ID, payload.getMessageId().toString());
493                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_CONSUMER_ID, sub.getConsumerInfo().getConsumerId().toString());
494                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_DESTINATION, baseDestination.getActiveMQDestination().getQualifiedName());
495
496                fireAdvisory(context, topic, payload, null, advisoryMessage);
497            }
498        } catch (Exception e) {
499            handleFireFailure("discarded", e);
500        }
501    }
502
503    @Override
504    public void slowConsumer(ConnectionContext context, Destination destination, Subscription subs) {
505        super.slowConsumer(context, destination, subs);
506        try {
507            if (!AdvisorySupport.isAdvisoryTopic(destination.getActiveMQDestination())) {
508                ActiveMQTopic topic = AdvisorySupport.getSlowConsumerAdvisoryTopic(destination.getActiveMQDestination());
509                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
510                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_CONSUMER_ID, subs.getConsumerInfo().getConsumerId().toString());
511                fireAdvisory(context, topic, subs.getConsumerInfo(), null, advisoryMessage);
512            }
513        } catch (Exception e) {
514            handleFireFailure("slow consumer", e);
515        }
516    }
517
518    @Override
519    public void fastProducer(ConnectionContext context, ProducerInfo producerInfo, ActiveMQDestination destination) {
520        super.fastProducer(context, producerInfo, destination);
521        try {
522            if (!AdvisorySupport.isAdvisoryTopic(destination)) {
523                ActiveMQTopic topic = AdvisorySupport.getFastProducerAdvisoryTopic(destination);
524                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
525                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_PRODUCER_ID, producerInfo.getProducerId().toString());
526                fireAdvisory(context, topic, producerInfo, null, advisoryMessage);
527            }
528        } catch (Exception e) {
529            handleFireFailure("fast producer", e);
530        }
531    }
532
533    private final IdGenerator connectionIdGenerator = new IdGenerator("advisory");
534    private final LongSequenceGenerator sessionIdGenerator = new LongSequenceGenerator();
535    private final LongSequenceGenerator consumerIdGenerator = new LongSequenceGenerator();
536
537    @Override
538    public void virtualDestinationAdded(ConnectionContext context,
539            VirtualDestination virtualDestination) {
540        super.virtualDestinationAdded(context, virtualDestination);
541
542        if (virtualDestinations.add(virtualDestination)) {
543            try {
544                // Don't advise advisory topics.
545                if (!AdvisorySupport.isAdvisoryTopic(virtualDestination.getVirtualDestination())) {
546
547                    //create demand for consumers on virtual destinations
548                    consumersLock.readLock().lock();
549                    try {
550                        //loop through existing destinations to see if any match this newly
551                        //created virtual destination
552                        if (getBrokerService().isUseVirtualDestSubsOnCreation()) {
553                            //for matches that are a queue, fire an advisory for demand
554                            for (ActiveMQDestination destination : destinations.keySet()) {
555                                if(destination.isQueue()) {
556                                    if (virtualDestinationMatcher.matches(virtualDestination, destination)) {
557                                        fireVirtualDestinationAddAdvisory(context, null, destination, virtualDestination);
558                                    }
559                                }
560                            }
561                        }
562
563                        //loop through existing consumers to see if any of them are consuming on a destination
564                        //that matches the new virtual destination
565                        for (Iterator<ConsumerInfo> iter = consumers.values().iterator(); iter.hasNext(); ) {
566                            ConsumerInfo info = iter.next();
567                            if (virtualDestinationMatcher.matches(virtualDestination, info.getDestination())) {
568                                fireVirtualDestinationAddAdvisory(context, info, info.getDestination(), virtualDestination);
569                            }
570                        }
571                    } finally {
572                        consumersLock.readLock().unlock();
573                    }
574                }
575            } catch (Exception e) {
576                handleFireFailure("virtualDestinationAdded", e);
577            }
578        }
579    }
580
581    private void fireVirtualDestinationAddAdvisory(ConnectionContext context, ConsumerInfo info, ActiveMQDestination activeMQDest,
582            VirtualDestination virtualDestination) throws Exception {
583        //if no consumer info, we need to create one - this is the case when an advisory is fired
584        //because of the existence of a destination matching a virtual destination
585        if (info == null) {
586            ConnectionId connectionId = new ConnectionId(connectionIdGenerator.generateId());
587            SessionId sessionId = new SessionId(connectionId, sessionIdGenerator.getNextSequenceId());
588            ConsumerId consumerId = new ConsumerId(sessionId, consumerIdGenerator.getNextSequenceId());
589
590            info = new ConsumerInfo(consumerId);
591
592            //store the virtual destination and the activeMQDestination as a pair so that we can keep track
593            //of all matching forwarded destinations that caused demand
594            if(brokerConsumerDests.putIfAbsent(new VirtualConsumerPair(virtualDestination, activeMQDest), info) == null) {
595                info.setDestination(virtualDestination.getVirtualDestination());
596                ActiveMQTopic topic = AdvisorySupport.getVirtualDestinationConsumerAdvisoryTopic(info.getDestination());
597
598                if (virtualDestinationConsumers.putIfAbsent(info, virtualDestination) == null) {
599                    fireConsumerAdvisory(context, info.getDestination(), topic, info);
600                }
601            }
602        //this is the case of a real consumer coming online
603        } else {
604            info = info.copy();
605            info.setDestination(virtualDestination.getVirtualDestination());
606            ActiveMQTopic topic = AdvisorySupport.getVirtualDestinationConsumerAdvisoryTopic(info.getDestination());
607
608            if (virtualDestinationConsumers.putIfAbsent(info, virtualDestination) == null) {
609                fireConsumerAdvisory(context, info.getDestination(), topic, info);
610            }
611        }
612    }
613
614    @Override
615    public void virtualDestinationRemoved(ConnectionContext context,
616            VirtualDestination virtualDestination) {
617        super.virtualDestinationRemoved(context, virtualDestination);
618
619        if (virtualDestinations.remove(virtualDestination)) {
620            try {
621                consumersLock.readLock().lock();
622                try {
623                    // remove the demand created by the addition of the virtual destination
624                    if (getBrokerService().isUseVirtualDestSubsOnCreation()) {
625                        if (!AdvisorySupport.isAdvisoryTopic(virtualDestination.getVirtualDestination())) {
626                            for (ConsumerInfo info : virtualDestinationConsumers.keySet()) {
627                                //find all consumers for this virtual destination
628                                if (virtualDestinationConsumers.get(info).equals(virtualDestination)) {
629                                    fireVirtualDestinationRemoveAdvisory(context, info);
630                                }
631
632                                //check consumers created for the existence of a destination to see if they
633                                //match the consumerinfo and clean up
634                                for (VirtualConsumerPair activeMQDest : brokerConsumerDests.keySet()) {
635                                    ConsumerInfo i = brokerConsumerDests.get(activeMQDest);
636                                    if (info.equals(i)) {
637                                        brokerConsumerDests.remove(activeMQDest);
638                                    }
639                                }
640                            }
641                        }
642                    }
643                } finally {
644                    consumersLock.readLock().unlock();
645                }
646            } catch (Exception e) {
647                handleFireFailure("virtualDestinationAdded", e);
648            }
649        }
650    }
651
652    private void fireVirtualDestinationRemoveAdvisory(ConnectionContext context,
653            ConsumerInfo info) throws Exception {
654
655        VirtualDestination virtualDestination = virtualDestinationConsumers.remove(info);
656        if (virtualDestination != null) {
657            ActiveMQTopic topic = AdvisorySupport.getVirtualDestinationConsumerAdvisoryTopic(virtualDestination.getVirtualDestination());
658
659            ActiveMQDestination dest = info.getDestination();
660
661            if (!dest.isTemporary() || destinations.containsKey(dest)) {
662                fireConsumerAdvisory(context, dest, topic, info.createRemoveCommand());
663            }
664        }
665    }
666
667    @Override
668    public void isFull(ConnectionContext context, Destination destination, Usage usage) {
669        super.isFull(context, destination, usage);
670        if (AdvisorySupport.isAdvisoryTopic(destination.getActiveMQDestination()) == false) {
671            try {
672
673                ActiveMQTopic topic = AdvisorySupport.getFullAdvisoryTopic(destination.getActiveMQDestination());
674                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
675                advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_USAGE_NAME, usage.getName());
676                advisoryMessage.setLongProperty(AdvisorySupport.MSG_PROPERTY_USAGE_COUNT, usage.getUsage());
677                fireAdvisory(context, topic, null, null, advisoryMessage);
678
679            } catch (Exception e) {
680                handleFireFailure("is full", e);
681            }
682        }
683    }
684
685    @Override
686    public void nowMasterBroker() {
687        super.nowMasterBroker();
688        try {
689            ActiveMQTopic topic = AdvisorySupport.getMasterBrokerAdvisoryTopic();
690            ActiveMQMessage advisoryMessage = new ActiveMQMessage();
691            ConnectionContext context = new ConnectionContext();
692            context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
693            context.setBroker(getBrokerService().getBroker());
694            fireAdvisory(context, topic, null, null, advisoryMessage);
695        } catch (Exception e) {
696            handleFireFailure("now master broker", e);
697        }
698    }
699
700    @Override
701    public boolean sendToDeadLetterQueue(ConnectionContext context, MessageReference messageReference,
702                                         Subscription subscription, Throwable poisonCause) {
703        boolean wasDLQd = super.sendToDeadLetterQueue(context, messageReference, subscription, poisonCause);
704        if (wasDLQd) {
705            try {
706                if (!messageReference.isAdvisory()) {
707                    BaseDestination baseDestination = (BaseDestination) messageReference.getMessage().getRegionDestination();
708                    ActiveMQTopic topic = AdvisorySupport.getMessageDLQdAdvisoryTopic(baseDestination.getActiveMQDestination());
709                    Message payload = messageReference.getMessage().copy();
710                    payload.clearBody();
711                    fireAdvisory(context, topic, payload);
712                }
713            } catch (Exception e) {
714                handleFireFailure("add to DLQ", e);
715            }
716        }
717
718        return wasDLQd;
719    }
720
721    @Override
722    public void networkBridgeStarted(BrokerInfo brokerInfo, boolean createdByDuplex, String remoteIp) {
723        try {
724            if (brokerInfo != null) {
725                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
726                advisoryMessage.setBooleanProperty("started", true);
727                advisoryMessage.setBooleanProperty("createdByDuplex", createdByDuplex);
728                advisoryMessage.setStringProperty("remoteIp", remoteIp);
729                networkBridges.putIfAbsent(brokerInfo, advisoryMessage);
730
731                ActiveMQTopic topic = AdvisorySupport.getNetworkBridgeAdvisoryTopic();
732
733                ConnectionContext context = new ConnectionContext();
734                context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
735                context.setBroker(getBrokerService().getBroker());
736                fireAdvisory(context, topic, brokerInfo, null, advisoryMessage);
737            }
738        } catch (Exception e) {
739            handleFireFailure("network bridge started", e);
740        }
741    }
742
743    @Override
744    public void networkBridgeStopped(BrokerInfo brokerInfo) {
745        try {
746            if (brokerInfo != null) {
747                ActiveMQMessage advisoryMessage = new ActiveMQMessage();
748                advisoryMessage.setBooleanProperty("started", false);
749                networkBridges.remove(brokerInfo);
750
751                ActiveMQTopic topic = AdvisorySupport.getNetworkBridgeAdvisoryTopic();
752
753                ConnectionContext context = new ConnectionContext();
754                context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
755                context.setBroker(getBrokerService().getBroker());
756                fireAdvisory(context, topic, brokerInfo, null, advisoryMessage);
757            }
758        } catch (Exception e) {
759            handleFireFailure("network bridge stopped", e);
760        }
761    }
762
763    private void handleFireFailure(String message, Throwable cause) {
764        LOG.warn("Failed to fire {} advisory, reason: {}", message, cause);
765        LOG.debug("{} detail: {}", message, cause, cause);
766    }
767
768    protected void fireAdvisory(ConnectionContext context, ActiveMQTopic topic, Command command) throws Exception {
769        fireAdvisory(context, topic, command, null);
770    }
771
772    protected void fireAdvisory(ConnectionContext context, ActiveMQTopic topic, Command command, ConsumerId targetConsumerId) throws Exception {
773        ActiveMQMessage advisoryMessage = new ActiveMQMessage();
774        fireAdvisory(context, topic, command, targetConsumerId, advisoryMessage);
775    }
776
777    protected void fireConsumerAdvisory(ConnectionContext context, ActiveMQDestination consumerDestination, ActiveMQTopic topic, Command command) throws Exception {
778        fireConsumerAdvisory(context, consumerDestination, topic, command, null);
779    }
780
781    protected void fireConsumerAdvisory(ConnectionContext context, ActiveMQDestination consumerDestination, ActiveMQTopic topic, Command command, ConsumerId targetConsumerId) throws Exception {
782        ActiveMQMessage advisoryMessage = new ActiveMQMessage();
783        int count = 0;
784        Set<Destination> set = getDestinations(consumerDestination);
785        if (set != null) {
786            for (Destination dest : set) {
787                count += dest.getDestinationStatistics().getConsumers().getCount();
788            }
789        }
790        advisoryMessage.setIntProperty(AdvisorySupport.MSG_PROPERTY_CONSUMER_COUNT, count);
791
792        fireAdvisory(context, topic, command, targetConsumerId, advisoryMessage);
793    }
794
795    protected void fireProducerAdvisory(ConnectionContext context, ActiveMQDestination producerDestination, ActiveMQTopic topic, Command command) throws Exception {
796        fireProducerAdvisory(context, producerDestination, topic, command, null);
797    }
798
799    protected void fireProducerAdvisory(ConnectionContext context, ActiveMQDestination producerDestination, ActiveMQTopic topic, Command command, ConsumerId targetConsumerId) throws Exception {
800        ActiveMQMessage advisoryMessage = new ActiveMQMessage();
801        int count = 0;
802        if (producerDestination != null) {
803            Set<Destination> set = getDestinations(producerDestination);
804            if (set != null) {
805                for (Destination dest : set) {
806                    count += dest.getDestinationStatistics().getProducers().getCount();
807                }
808            }
809        }
810        advisoryMessage.setIntProperty("producerCount", count);
811        fireAdvisory(context, topic, command, targetConsumerId, advisoryMessage);
812    }
813
814    public void fireAdvisory(ConnectionContext context, ActiveMQTopic topic, Command command, ConsumerId targetConsumerId, ActiveMQMessage advisoryMessage) throws Exception {
815        //set properties
816        advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_NAME, getBrokerName());
817        String id = getBrokerId() != null ? getBrokerId().getValue() : "NOT_SET";
818        advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_ID, id);
819
820        String url = getBrokerService().getVmConnectorURI().toString();
821        if (getBrokerService().getDefaultSocketURIString() != null) {
822            url = getBrokerService().getDefaultSocketURIString();
823        }
824        advisoryMessage.setStringProperty(AdvisorySupport.MSG_PROPERTY_ORIGIN_BROKER_URL, url);
825
826        //set the data structure
827        advisoryMessage.setDataStructure(command);
828        advisoryMessage.setPersistent(false);
829        advisoryMessage.setType(AdvisorySupport.ADIVSORY_MESSAGE_TYPE);
830        advisoryMessage.setMessageId(new MessageId(advisoryProducerId, messageIdGenerator.getNextSequenceId()));
831        advisoryMessage.setTargetConsumerId(targetConsumerId);
832        advisoryMessage.setDestination(topic);
833        advisoryMessage.setResponseRequired(false);
834        advisoryMessage.setProducerId(advisoryProducerId);
835        boolean originalFlowControl = context.isProducerFlowControl();
836        final ProducerBrokerExchange producerExchange = new ProducerBrokerExchange();
837        producerExchange.setConnectionContext(context);
838        producerExchange.setMutable(true);
839        producerExchange.setProducerState(new ProducerState(new ProducerInfo()));
840        try {
841            context.setProducerFlowControl(false);
842            next.send(producerExchange, advisoryMessage);
843        } finally {
844            context.setProducerFlowControl(originalFlowControl);
845        }
846    }
847
848    public Map<ConnectionId, ConnectionInfo> getAdvisoryConnections() {
849        return connections;
850    }
851
852    public Collection<ConsumerInfo> getAdvisoryConsumers() {
853        consumersLock.readLock().lock();
854        try {
855            return new ArrayList<ConsumerInfo>(consumers.values());
856        } finally {
857            consumersLock.readLock().unlock();
858        }
859    }
860
861    public Map<ProducerId, ProducerInfo> getAdvisoryProducers() {
862        return producers;
863    }
864
865    public Map<ActiveMQDestination, DestinationInfo> getAdvisoryDestinations() {
866        return destinations;
867    }
868
869    private class VirtualConsumerPair {
870        private final VirtualDestination virtualDestination;
871
872        //destination that matches this virtualDestination as part target
873        //this is so we can keep track of more than one destination that might
874        //match the virtualDestination and cause demand
875        private final ActiveMQDestination activeMQDestination;
876
877        public VirtualConsumerPair(VirtualDestination virtualDestination,
878                ActiveMQDestination activeMQDestination) {
879            super();
880            this.virtualDestination = virtualDestination;
881            this.activeMQDestination = activeMQDestination;
882        }
883        @Override
884        public int hashCode() {
885            final int prime = 31;
886            int result = 1;
887            result = prime * result + getOuterType().hashCode();
888            result = prime
889                    * result
890                    + ((activeMQDestination == null) ? 0 : activeMQDestination
891                            .hashCode());
892            result = prime
893                    * result
894                    + ((virtualDestination == null) ? 0 : virtualDestination
895                            .hashCode());
896            return result;
897        }
898        @Override
899        public boolean equals(Object obj) {
900            if (this == obj)
901                return true;
902            if (obj == null)
903                return false;
904            if (getClass() != obj.getClass())
905                return false;
906            VirtualConsumerPair other = (VirtualConsumerPair) obj;
907            if (!getOuterType().equals(other.getOuterType()))
908                return false;
909            if (activeMQDestination == null) {
910                if (other.activeMQDestination != null)
911                    return false;
912            } else if (!activeMQDestination.equals(other.activeMQDestination))
913                return false;
914            if (virtualDestination == null) {
915                if (other.virtualDestination != null)
916                    return false;
917            } else if (!virtualDestination.equals(other.virtualDestination))
918                return false;
919            return true;
920        }
921        private AdvisoryBroker getOuterType() {
922            return AdvisoryBroker.this;
923        }
924    }
925}