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