001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.broker.region;
018
019import java.io.IOException;
020import java.net.URI;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.CopyOnWriteArrayList;
030import java.util.concurrent.ThreadPoolExecutor;
031import java.util.concurrent.atomic.AtomicBoolean;
032import java.util.concurrent.locks.ReentrantReadWriteLock;
033
034import javax.jms.InvalidClientIDException;
035import javax.jms.JMSException;
036
037import org.apache.activemq.broker.Broker;
038import org.apache.activemq.broker.BrokerService;
039import org.apache.activemq.broker.Connection;
040import org.apache.activemq.broker.ConnectionContext;
041import org.apache.activemq.broker.ConsumerBrokerExchange;
042import org.apache.activemq.broker.EmptyBroker;
043import org.apache.activemq.broker.ProducerBrokerExchange;
044import org.apache.activemq.broker.TransportConnection;
045import org.apache.activemq.broker.TransportConnector;
046import org.apache.activemq.broker.region.policy.DeadLetterStrategy;
047import org.apache.activemq.broker.region.policy.PolicyMap;
048import org.apache.activemq.command.ActiveMQDestination;
049import org.apache.activemq.command.ActiveMQMessage;
050import org.apache.activemq.command.BrokerId;
051import org.apache.activemq.command.BrokerInfo;
052import org.apache.activemq.command.ConnectionId;
053import org.apache.activemq.command.ConnectionInfo;
054import org.apache.activemq.command.ConsumerControl;
055import org.apache.activemq.command.ConsumerInfo;
056import org.apache.activemq.command.DestinationInfo;
057import org.apache.activemq.command.Message;
058import org.apache.activemq.command.MessageAck;
059import org.apache.activemq.command.MessageDispatch;
060import org.apache.activemq.command.MessageDispatchNotification;
061import org.apache.activemq.command.MessagePull;
062import org.apache.activemq.command.ProducerInfo;
063import org.apache.activemq.command.RemoveSubscriptionInfo;
064import org.apache.activemq.command.Response;
065import org.apache.activemq.command.TransactionId;
066import org.apache.activemq.state.ConnectionState;
067import org.apache.activemq.store.PListStore;
068import org.apache.activemq.thread.Scheduler;
069import org.apache.activemq.thread.TaskRunnerFactory;
070import org.apache.activemq.transport.TransmitCallback;
071import org.apache.activemq.usage.SystemUsage;
072import org.apache.activemq.util.BrokerSupport;
073import org.apache.activemq.util.IdGenerator;
074import org.apache.activemq.util.InetAddressUtil;
075import org.apache.activemq.util.LongSequenceGenerator;
076import org.apache.activemq.util.ServiceStopper;
077import org.slf4j.Logger;
078import org.slf4j.LoggerFactory;
079
080/**
081 * Routes Broker operations to the correct messaging regions for processing.
082 */
083public class RegionBroker extends EmptyBroker {
084    public static final String ORIGINAL_EXPIRATION = "originalExpiration";
085    private static final Logger LOG = LoggerFactory.getLogger(RegionBroker.class);
086    private static final IdGenerator BROKER_ID_GENERATOR = new IdGenerator();
087
088    protected final DestinationStatistics destinationStatistics = new DestinationStatistics();
089    protected DestinationFactory destinationFactory;
090    protected final Map<ConnectionId, ConnectionState> connectionStates = Collections.synchronizedMap(new HashMap<ConnectionId, ConnectionState>());
091
092    private final Region queueRegion;
093    private final Region topicRegion;
094    private final Region tempQueueRegion;
095    private final Region tempTopicRegion;
096    protected final BrokerService brokerService;
097    private boolean started;
098    private boolean keepDurableSubsActive;
099
100    private final CopyOnWriteArrayList<Connection> connections = new CopyOnWriteArrayList<Connection>();
101    private final Map<ActiveMQDestination, ActiveMQDestination> destinationGate = new HashMap<ActiveMQDestination, ActiveMQDestination>();
102    private final Map<ActiveMQDestination, Destination> destinations = new ConcurrentHashMap<ActiveMQDestination, Destination>();
103    private final Map<BrokerId, BrokerInfo> brokerInfos = new HashMap<BrokerId, BrokerInfo>();
104
105    private final LongSequenceGenerator sequenceGenerator = new LongSequenceGenerator();
106    private BrokerId brokerId;
107    private String brokerName;
108    private final Map<String, ConnectionContext> clientIdSet = new HashMap<String, ConnectionContext>();
109    private final DestinationInterceptor destinationInterceptor;
110    private ConnectionContext adminConnectionContext;
111    private final Scheduler scheduler;
112    private final ThreadPoolExecutor executor;
113    private boolean allowTempAutoCreationOnSend;
114
115    private final ReentrantReadWriteLock inactiveDestinationsPurgeLock = new ReentrantReadWriteLock();
116    private final TaskRunnerFactory taskRunnerFactory;
117    private final AtomicBoolean purgeInactiveDestinationsTaskInProgress = new AtomicBoolean(false);
118    private final Runnable purgeInactiveDestinationsTask = new Runnable() {
119        @Override
120        public void run() {
121            if (purgeInactiveDestinationsTaskInProgress.compareAndSet(false, true)) {
122                taskRunnerFactory.execute(purgeInactiveDestinationsWork);
123            }
124        }
125    };
126    private final Runnable purgeInactiveDestinationsWork = new Runnable() {
127        @Override
128        public void run() {
129            try {
130                purgeInactiveDestinations();
131            } catch (Throwable ignored) {
132                LOG.error("Unexpected exception on purgeInactiveDestinations {}", this, ignored);
133            } finally {
134                purgeInactiveDestinationsTaskInProgress.set(false);
135            }
136        }
137    };
138
139    public RegionBroker(BrokerService brokerService, TaskRunnerFactory taskRunnerFactory, SystemUsage memoryManager, DestinationFactory destinationFactory,
140        DestinationInterceptor destinationInterceptor, Scheduler scheduler, ThreadPoolExecutor executor) throws IOException {
141        this.brokerService = brokerService;
142        this.executor = executor;
143        this.scheduler = scheduler;
144        if (destinationFactory == null) {
145            throw new IllegalArgumentException("null destinationFactory");
146        }
147        this.sequenceGenerator.setLastSequenceId(destinationFactory.getLastMessageBrokerSequenceId());
148        this.destinationFactory = destinationFactory;
149        queueRegion = createQueueRegion(memoryManager, taskRunnerFactory, destinationFactory);
150        topicRegion = createTopicRegion(memoryManager, taskRunnerFactory, destinationFactory);
151        this.destinationInterceptor = destinationInterceptor;
152        tempQueueRegion = createTempQueueRegion(memoryManager, taskRunnerFactory, destinationFactory);
153        tempTopicRegion = createTempTopicRegion(memoryManager, taskRunnerFactory, destinationFactory);
154        this.taskRunnerFactory = taskRunnerFactory;
155    }
156
157    @Override
158    public Map<ActiveMQDestination, Destination> getDestinationMap() {
159        Map<ActiveMQDestination, Destination> answer = new HashMap<ActiveMQDestination, Destination>(getQueueRegion().getDestinationMap());
160        answer.putAll(getTopicRegion().getDestinationMap());
161        return answer;
162    }
163
164    @Override
165    public Map<ActiveMQDestination, Destination> getDestinationMap(ActiveMQDestination destination) {
166        try {
167            return getRegion(destination).getDestinationMap();
168        } catch (JMSException jmse) {
169            return Collections.emptyMap();
170        }
171    }
172
173    @Override
174    public Set<Destination> getDestinations(ActiveMQDestination destination) {
175        try {
176            return getRegion(destination).getDestinations(destination);
177        } catch (JMSException jmse) {
178            return Collections.emptySet();
179        }
180    }
181
182    public Region getQueueRegion() {
183        return queueRegion;
184    }
185
186    public Region getTempQueueRegion() {
187        return tempQueueRegion;
188    }
189
190    public Region getTempTopicRegion() {
191        return tempTopicRegion;
192    }
193
194    public Region getTopicRegion() {
195        return topicRegion;
196    }
197
198    protected Region createTempTopicRegion(SystemUsage memoryManager, TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) {
199        return new TempTopicRegion(this, destinationStatistics, memoryManager, taskRunnerFactory, destinationFactory);
200    }
201
202    protected Region createTempQueueRegion(SystemUsage memoryManager, TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) {
203        return new TempQueueRegion(this, destinationStatistics, memoryManager, taskRunnerFactory, destinationFactory);
204    }
205
206    protected Region createTopicRegion(SystemUsage memoryManager, TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) {
207        return new TopicRegion(this, destinationStatistics, memoryManager, taskRunnerFactory, destinationFactory);
208    }
209
210    protected Region createQueueRegion(SystemUsage memoryManager, TaskRunnerFactory taskRunnerFactory, DestinationFactory destinationFactory) {
211        return new QueueRegion(this, destinationStatistics, memoryManager, taskRunnerFactory, destinationFactory);
212    }
213
214    @Override
215    public void start() throws Exception {
216        started = true;
217        queueRegion.start();
218        topicRegion.start();
219        tempQueueRegion.start();
220        tempTopicRegion.start();
221        int period = this.brokerService.getSchedulePeriodForDestinationPurge();
222        if (period > 0) {
223            this.scheduler.executePeriodically(purgeInactiveDestinationsTask, period);
224        }
225    }
226
227    @Override
228    public void stop() throws Exception {
229        started = false;
230        this.scheduler.cancel(purgeInactiveDestinationsTask);
231        ServiceStopper ss = new ServiceStopper();
232        doStop(ss);
233        ss.throwFirstException();
234        // clear the state
235        clientIdSet.clear();
236        connections.clear();
237        destinations.clear();
238        brokerInfos.clear();
239    }
240
241    public PolicyMap getDestinationPolicy() {
242        return brokerService != null ? brokerService.getDestinationPolicy() : null;
243    }
244
245    public ConnectionContext getConnectionContext(String clientId) {
246        return clientIdSet.get(clientId);
247    }
248
249    @Override
250    public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
251        String clientId = info.getClientId();
252        if (clientId == null) {
253            throw new InvalidClientIDException("No clientID specified for connection request");
254        }
255
256        ConnectionContext oldContext = null;
257
258        synchronized (clientIdSet) {
259            oldContext = clientIdSet.get(clientId);
260            if (oldContext != null) {
261                if (context.isAllowLinkStealing()) {
262                    clientIdSet.put(clientId, context);
263                } else {
264                    throw new InvalidClientIDException("Broker: " + getBrokerName() + " - Client: " + clientId + " already connected from "
265                        + oldContext.getConnection().getRemoteAddress());
266                }
267            } else {
268                clientIdSet.put(clientId, context);
269            }
270        }
271
272        if (oldContext != null) {
273            if (oldContext.getConnection() != null) {
274                Connection connection = oldContext.getConnection();
275                LOG.warn("Stealing link for clientId {} From Connection {}", clientId, oldContext.getConnection());
276                if (connection instanceof TransportConnection) {
277                    TransportConnection transportConnection = (TransportConnection) connection;
278                    transportConnection.stopAsync(new IOException("Stealing link for clientId " + clientId + " From Connection " + oldContext.getConnection().getConnectionId()));
279                } else {
280                    connection.stop();
281                }
282            } else {
283                LOG.error("No Connection found for {}", oldContext);
284            }
285        }
286
287        connections.add(context.getConnection());
288    }
289
290    @Override
291    public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
292        String clientId = info.getClientId();
293        if (clientId == null) {
294            throw new InvalidClientIDException("No clientID specified for connection disconnect request");
295        }
296        synchronized (clientIdSet) {
297            ConnectionContext oldValue = clientIdSet.get(clientId);
298            // we may be removing the duplicate connection, not the first connection to be created
299            // so lets check that their connection IDs are the same
300            if (oldValue == context) {
301                if (isEqual(oldValue.getConnectionId(), info.getConnectionId())) {
302                    clientIdSet.remove(clientId);
303                }
304            }
305        }
306        connections.remove(context.getConnection());
307    }
308
309    protected boolean isEqual(ConnectionId connectionId, ConnectionId connectionId2) {
310        return connectionId == connectionId2 || (connectionId != null && connectionId.equals(connectionId2));
311    }
312
313    @Override
314    public Connection[] getClients() throws Exception {
315        ArrayList<Connection> l = new ArrayList<Connection>(connections);
316        Connection rc[] = new Connection[l.size()];
317        l.toArray(rc);
318        return rc;
319    }
320
321    @Override
322    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, boolean createIfTemp) throws Exception {
323
324        Destination answer;
325
326        answer = destinations.get(destination);
327        if (answer != null) {
328            return answer;
329        }
330
331        synchronized (destinationGate) {
332            answer = destinations.get(destination);
333            if (answer != null) {
334                return answer;
335            }
336
337            if (destinationGate.get(destination) != null) {
338                // Guard against spurious wakeup.
339                while (destinationGate.containsKey(destination)) {
340                    destinationGate.wait();
341                }
342                answer = destinations.get(destination);
343                if (answer != null) {
344                    return answer;
345                } else {
346                    // In case of intermediate remove or add failure
347                    destinationGate.put(destination, destination);
348                }
349            }
350        }
351
352        try {
353            boolean create = true;
354            if (destination.isTemporary()) {
355                create = createIfTemp;
356            }
357            answer = getRegion(destination).addDestination(context, destination, create);
358            destinations.put(destination, answer);
359        } finally {
360            synchronized (destinationGate) {
361                destinationGate.remove(destination);
362                destinationGate.notifyAll();
363            }
364        }
365
366        return answer;
367    }
368
369    @Override
370    public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception {
371        if (destinations.containsKey(destination)) {
372            getRegion(destination).removeDestination(context, destination, timeout);
373            destinations.remove(destination);
374        }
375    }
376
377    @Override
378    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
379        addDestination(context, info.getDestination(), true);
380
381    }
382
383    @Override
384    public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
385        removeDestination(context, info.getDestination(), info.getTimeout());
386    }
387
388    @Override
389    public ActiveMQDestination[] getDestinations() throws Exception {
390        ArrayList<ActiveMQDestination> l;
391
392        l = new ArrayList<ActiveMQDestination>(getDestinationMap().keySet());
393
394        ActiveMQDestination rc[] = new ActiveMQDestination[l.size()];
395        l.toArray(rc);
396        return rc;
397    }
398
399    @Override
400    public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
401        ActiveMQDestination destination = info.getDestination();
402        if (destination != null) {
403            inactiveDestinationsPurgeLock.readLock().lock();
404            try {
405                // This seems to cause the destination to be added but without
406                // advisories firing...
407                context.getBroker().addDestination(context, destination, isAllowTempAutoCreationOnSend());
408                getRegion(destination).addProducer(context, info);
409            } finally {
410                inactiveDestinationsPurgeLock.readLock().unlock();
411            }
412        }
413    }
414
415    @Override
416    public void removeProducer(ConnectionContext context, ProducerInfo info) throws Exception {
417        ActiveMQDestination destination = info.getDestination();
418        if (destination != null) {
419            inactiveDestinationsPurgeLock.readLock().lock();
420            try {
421                getRegion(destination).removeProducer(context, info);
422            } finally {
423                inactiveDestinationsPurgeLock.readLock().unlock();
424            }
425        }
426    }
427
428    @Override
429    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
430        ActiveMQDestination destination = info.getDestination();
431        if (destinationInterceptor != null) {
432            destinationInterceptor.create(this, context, destination);
433        }
434        inactiveDestinationsPurgeLock.readLock().lock();
435        try {
436            return getRegion(destination).addConsumer(context, info);
437        } finally {
438            inactiveDestinationsPurgeLock.readLock().unlock();
439        }
440    }
441
442    @Override
443    public void removeConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
444        ActiveMQDestination destination = info.getDestination();
445        inactiveDestinationsPurgeLock.readLock().lock();
446        try {
447            getRegion(destination).removeConsumer(context, info);
448        } finally {
449            inactiveDestinationsPurgeLock.readLock().unlock();
450        }
451    }
452
453    @Override
454    public void removeSubscription(ConnectionContext context, RemoveSubscriptionInfo info) throws Exception {
455        inactiveDestinationsPurgeLock.readLock().lock();
456        try {
457            topicRegion.removeSubscription(context, info);
458        } finally {
459            inactiveDestinationsPurgeLock.readLock().unlock();
460
461        }
462    }
463
464    @Override
465    public void send(ProducerBrokerExchange producerExchange, Message message) throws Exception {
466        ActiveMQDestination destination = message.getDestination();
467        message.setBrokerInTime(System.currentTimeMillis());
468        if (producerExchange.isMutable() || producerExchange.getRegion() == null
469            || (producerExchange.getRegionDestination() != null && producerExchange.getRegionDestination().isDisposed())) {
470            // ensure the destination is registered with the RegionBroker
471            producerExchange.getConnectionContext().getBroker()
472                .addDestination(producerExchange.getConnectionContext(), destination, isAllowTempAutoCreationOnSend());
473            producerExchange.setRegion(getRegion(destination));
474            producerExchange.setRegionDestination(null);
475        }
476
477        producerExchange.getRegion().send(producerExchange, message);
478
479        // clean up so these references aren't kept (possible leak) in the producer exchange
480        // especially since temps are transitory
481        if (producerExchange.isMutable()) {
482            producerExchange.setRegionDestination(null);
483            producerExchange.setRegion(null);
484        }
485    }
486
487    @Override
488    public void acknowledge(ConsumerBrokerExchange consumerExchange, MessageAck ack) throws Exception {
489        if (consumerExchange.isWildcard() || consumerExchange.getRegion() == null) {
490            ActiveMQDestination destination = ack.getDestination();
491            consumerExchange.setRegion(getRegion(destination));
492        }
493        consumerExchange.getRegion().acknowledge(consumerExchange, ack);
494    }
495
496    public Region getRegion(ActiveMQDestination destination) throws JMSException {
497        switch (destination.getDestinationType()) {
498            case ActiveMQDestination.QUEUE_TYPE:
499                return queueRegion;
500            case ActiveMQDestination.TOPIC_TYPE:
501                return topicRegion;
502            case ActiveMQDestination.TEMP_QUEUE_TYPE:
503                return tempQueueRegion;
504            case ActiveMQDestination.TEMP_TOPIC_TYPE:
505                return tempTopicRegion;
506            default:
507                throw createUnknownDestinationTypeException(destination);
508        }
509    }
510
511    @Override
512    public Response messagePull(ConnectionContext context, MessagePull pull) throws Exception {
513        ActiveMQDestination destination = pull.getDestination();
514        return getRegion(destination).messagePull(context, pull);
515    }
516
517    @Override
518    public TransactionId[] getPreparedTransactions(ConnectionContext context) throws Exception {
519        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
520    }
521
522    @Override
523    public void beginTransaction(ConnectionContext context, TransactionId xid) throws Exception {
524        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
525    }
526
527    @Override
528    public int prepareTransaction(ConnectionContext context, TransactionId xid) throws Exception {
529        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
530    }
531
532    @Override
533    public void rollbackTransaction(ConnectionContext context, TransactionId xid) throws Exception {
534        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
535    }
536
537    @Override
538    public void commitTransaction(ConnectionContext context, TransactionId xid, boolean onePhase) throws Exception {
539        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
540    }
541
542    @Override
543    public void forgetTransaction(ConnectionContext context, TransactionId transactionId) throws Exception {
544        throw new IllegalAccessException("Transaction operation not implemented by this broker.");
545    }
546
547    @Override
548    public void gc() {
549        queueRegion.gc();
550        topicRegion.gc();
551    }
552
553    @Override
554    public BrokerId getBrokerId() {
555        if (brokerId == null) {
556            brokerId = new BrokerId(BROKER_ID_GENERATOR.generateId());
557        }
558        return brokerId;
559    }
560
561    public void setBrokerId(BrokerId brokerId) {
562        this.brokerId = brokerId;
563    }
564
565    @Override
566    public String getBrokerName() {
567        if (brokerName == null) {
568            try {
569                brokerName = InetAddressUtil.getLocalHostName().toLowerCase(Locale.ENGLISH);
570            } catch (Exception e) {
571                brokerName = "localhost";
572            }
573        }
574        return brokerName;
575    }
576
577    public void setBrokerName(String brokerName) {
578        this.brokerName = brokerName;
579    }
580
581    public DestinationStatistics getDestinationStatistics() {
582        return destinationStatistics;
583    }
584
585    protected JMSException createUnknownDestinationTypeException(ActiveMQDestination destination) {
586        return new JMSException("Unknown destination type: " + destination.getDestinationType());
587    }
588
589    @Override
590    public synchronized void addBroker(Connection connection, BrokerInfo info) {
591        BrokerInfo existing = brokerInfos.get(info.getBrokerId());
592        if (existing == null) {
593            existing = info.copy();
594            existing.setPeerBrokerInfos(null);
595            brokerInfos.put(info.getBrokerId(), existing);
596        }
597        existing.incrementRefCount();
598        LOG.debug("{} addBroker: {} brokerInfo size: {}", new Object[]{ getBrokerName(), info.getBrokerName(), brokerInfos.size() });
599        addBrokerInClusterUpdate(info);
600    }
601
602    @Override
603    public synchronized void removeBroker(Connection connection, BrokerInfo info) {
604        if (info != null) {
605            BrokerInfo existing = brokerInfos.get(info.getBrokerId());
606            if (existing != null && existing.decrementRefCount() == 0) {
607                brokerInfos.remove(info.getBrokerId());
608            }
609            LOG.debug("{} removeBroker: {} brokerInfo size: {}", new Object[]{ getBrokerName(), info.getBrokerName(), brokerInfos.size()});
610            // When stopping don't send cluster updates since we are the one's tearing down
611            // our own bridges.
612            if (!brokerService.isStopping()) {
613                removeBrokerInClusterUpdate(info);
614            }
615        }
616    }
617
618    @Override
619    public synchronized BrokerInfo[] getPeerBrokerInfos() {
620        BrokerInfo[] result = new BrokerInfo[brokerInfos.size()];
621        result = brokerInfos.values().toArray(result);
622        return result;
623    }
624
625    @Override
626    public void preProcessDispatch(final MessageDispatch messageDispatch) {
627        final Message message = messageDispatch.getMessage();
628        if (message != null) {
629            long endTime = System.currentTimeMillis();
630            message.setBrokerOutTime(endTime);
631            if (getBrokerService().isEnableStatistics()) {
632                long totalTime = endTime - message.getBrokerInTime();
633                ((Destination) message.getRegionDestination()).getDestinationStatistics().getProcessTime().addTime(totalTime);
634            }
635            if (((BaseDestination) message.getRegionDestination()).isPersistJMSRedelivered() && !message.isRedelivered()) {
636                final int originalValue = message.getRedeliveryCounter();
637                message.incrementRedeliveryCounter();
638                try {
639                    if (message.isPersistent()) {
640                        ((BaseDestination) message.getRegionDestination()).getMessageStore().updateMessage(message);
641                    }
642                    messageDispatch.setTransmitCallback(new TransmitCallback() {
643                        // dispatch is considered a delivery, so update sub state post dispatch otherwise
644                        // on a disconnect/reconnect cached messages will not reflect initial delivery attempt
645                        final TransmitCallback delegate = messageDispatch.getTransmitCallback();
646                        @Override
647                        public void onSuccess() {
648                            message.incrementRedeliveryCounter();
649                            if (delegate != null) {
650                                delegate.onSuccess();
651                            }
652                        }
653
654                        @Override
655                        public void onFailure() {
656                            if (delegate != null) {
657                                delegate.onFailure();
658                            }
659                        }
660                    });
661                } catch (IOException error) {
662                    RuntimeException runtimeException = new RuntimeException("Failed to persist JMSRedeliveryFlag on " + message.getMessageId() + " in " + message.getDestination(), error);
663                    LOG.warn(runtimeException.getLocalizedMessage(), runtimeException);
664                    throw runtimeException;
665                } finally {
666                    message.setRedeliveryCounter(originalValue);
667                }
668            }
669        }
670    }
671
672    @Override
673    public void postProcessDispatch(MessageDispatch messageDispatch) {
674    }
675
676    @Override
677    public void processDispatchNotification(MessageDispatchNotification messageDispatchNotification) throws Exception {
678        ActiveMQDestination destination = messageDispatchNotification.getDestination();
679        getRegion(destination).processDispatchNotification(messageDispatchNotification);
680    }
681
682    @Override
683    public boolean isStopped() {
684        return !started;
685    }
686
687    @Override
688    public Set<ActiveMQDestination> getDurableDestinations() {
689        return destinationFactory.getDestinations();
690    }
691
692    protected void doStop(ServiceStopper ss) {
693        ss.stop(queueRegion);
694        ss.stop(topicRegion);
695        ss.stop(tempQueueRegion);
696        ss.stop(tempTopicRegion);
697    }
698
699    public boolean isKeepDurableSubsActive() {
700        return keepDurableSubsActive;
701    }
702
703    public void setKeepDurableSubsActive(boolean keepDurableSubsActive) {
704        this.keepDurableSubsActive = keepDurableSubsActive;
705        ((TopicRegion) topicRegion).setKeepDurableSubsActive(keepDurableSubsActive);
706    }
707
708    public DestinationInterceptor getDestinationInterceptor() {
709        return destinationInterceptor;
710    }
711
712    @Override
713    public ConnectionContext getAdminConnectionContext() {
714        return adminConnectionContext;
715    }
716
717    @Override
718    public void setAdminConnectionContext(ConnectionContext adminConnectionContext) {
719        this.adminConnectionContext = adminConnectionContext;
720    }
721
722    public Map<ConnectionId, ConnectionState> getConnectionStates() {
723        return connectionStates;
724    }
725
726    @Override
727    public PListStore getTempDataStore() {
728        return brokerService.getTempDataStore();
729    }
730
731    @Override
732    public URI getVmConnectorURI() {
733        return brokerService.getVmConnectorURI();
734    }
735
736    @Override
737    public void brokerServiceStarted() {
738    }
739
740    @Override
741    public BrokerService getBrokerService() {
742        return brokerService;
743    }
744
745    @Override
746    public boolean isExpired(MessageReference messageReference) {
747        return messageReference.canProcessAsExpired();
748    }
749
750    private boolean stampAsExpired(Message message) throws IOException {
751        boolean stamped = false;
752        if (message.getProperty(ORIGINAL_EXPIRATION) == null) {
753            long expiration = message.getExpiration();
754            message.setProperty(ORIGINAL_EXPIRATION, new Long(expiration));
755            stamped = true;
756        }
757        return stamped;
758    }
759
760    @Override
761    public void messageExpired(ConnectionContext context, MessageReference node, Subscription subscription) {
762        LOG.debug("Message expired {}", node);
763        getRoot().sendToDeadLetterQueue(context, node, subscription, new Throwable("Message Expired. Expiration:" + node.getExpiration()));
764    }
765
766    @Override
767    public boolean sendToDeadLetterQueue(ConnectionContext context, MessageReference node, Subscription subscription, Throwable poisonCause) {
768        try {
769            if (node != null) {
770                Message message = node.getMessage();
771                if (message != null && node.getRegionDestination() != null) {
772                    DeadLetterStrategy deadLetterStrategy = ((Destination) node.getRegionDestination()).getDeadLetterStrategy();
773                    if (deadLetterStrategy != null) {
774                        if (deadLetterStrategy.isSendToDeadLetterQueue(message)) {
775                            ActiveMQDestination deadLetterDestination = deadLetterStrategy.getDeadLetterQueueFor(message, subscription);
776                            // Prevent a DLQ loop where same message is sent from a DLQ back to itself
777                            if (deadLetterDestination.equals(message.getDestination())) {
778                                LOG.debug("Not re-adding to DLQ: {}, dest: {}", message.getMessageId(), message.getDestination());
779                                return false;
780                            }
781
782                            // message may be inflight to other subscriptions so do not modify
783                            message = message.copy();
784                            long dlqExpiration = deadLetterStrategy.getExpiration();
785                            if (dlqExpiration > 0) {
786                                dlqExpiration += System.currentTimeMillis();
787                            } else {
788                                stampAsExpired(message);
789                            }
790                            message.setExpiration(dlqExpiration);
791                            if (!message.isPersistent()) {
792                                message.setPersistent(true);
793                                message.setProperty("originalDeliveryMode", "NON_PERSISTENT");
794                            }
795                            if (poisonCause != null) {
796                                message.setProperty(ActiveMQMessage.DLQ_DELIVERY_FAILURE_CAUSE_PROPERTY,
797                                        poisonCause.toString());
798                            }
799                            // The original destination and transaction id do
800                            // not get filled when the message is first sent,
801                            // it is only populated if the message is routed to
802                            // another destination like the DLQ
803                            ConnectionContext adminContext = context;
804                            if (context.getSecurityContext() == null || !context.getSecurityContext().isBrokerContext()) {
805                                adminContext = BrokerSupport.getConnectionContext(this);
806                            }
807                            addDestination(adminContext, deadLetterDestination, false).getActiveMQDestination().setDLQ(true);
808                            BrokerSupport.resendNoCopy(adminContext, message, deadLetterDestination);
809                            return true;
810                        }
811                    } else {
812                        LOG.debug("Dead Letter message with no DLQ strategy in place, message id: {}, destination: {}", message.getMessageId(), message.getDestination());
813                    }
814                }
815            }
816        } catch (Exception e) {
817            LOG.warn("Caught an exception sending to DLQ: {}", node, e);
818        }
819
820        return false;
821    }
822
823    @Override
824    public Broker getRoot() {
825        try {
826            return getBrokerService().getBroker();
827        } catch (Exception e) {
828            LOG.error("Trying to get Root Broker", e);
829            throw new RuntimeException("The broker from the BrokerService should not throw an exception", e);
830        }
831    }
832
833    /**
834     * @return the broker sequence id
835     */
836    @Override
837    public long getBrokerSequenceId() {
838        synchronized (sequenceGenerator) {
839            return sequenceGenerator.getNextSequenceId();
840        }
841    }
842
843    @Override
844    public Scheduler getScheduler() {
845        return this.scheduler;
846    }
847
848    @Override
849    public ThreadPoolExecutor getExecutor() {
850        return this.executor;
851    }
852
853    @Override
854    public void processConsumerControl(ConsumerBrokerExchange consumerExchange, ConsumerControl control) {
855        ActiveMQDestination destination = control.getDestination();
856        try {
857            getRegion(destination).processConsumerControl(consumerExchange, control);
858        } catch (JMSException jmse) {
859            LOG.warn("unmatched destination: {}, in consumerControl: {}", destination, control);
860        }
861    }
862
863    protected void addBrokerInClusterUpdate(BrokerInfo info) {
864        List<TransportConnector> connectors = this.brokerService.getTransportConnectors();
865        for (TransportConnector connector : connectors) {
866            if (connector.isUpdateClusterClients()) {
867                connector.addPeerBroker(info);
868                connector.updateClientClusterInfo();
869            }
870        }
871    }
872
873    protected void removeBrokerInClusterUpdate(BrokerInfo info) {
874        List<TransportConnector> connectors = this.brokerService.getTransportConnectors();
875        for (TransportConnector connector : connectors) {
876            if (connector.isUpdateClusterClients() && connector.isUpdateClusterClientsOnRemove()) {
877                connector.removePeerBroker(info);
878                connector.updateClientClusterInfo();
879            }
880        }
881    }
882
883    protected void purgeInactiveDestinations() {
884        inactiveDestinationsPurgeLock.writeLock().lock();
885        try {
886            List<Destination> list = new ArrayList<Destination>();
887            Map<ActiveMQDestination, Destination> map = getDestinationMap();
888            if (isAllowTempAutoCreationOnSend()) {
889                map.putAll(tempQueueRegion.getDestinationMap());
890                map.putAll(tempTopicRegion.getDestinationMap());
891            }
892            long maxPurgedDests = this.brokerService.getMaxPurgedDestinationsPerSweep();
893            long timeStamp = System.currentTimeMillis();
894            for (Destination d : map.values()) {
895                d.markForGC(timeStamp);
896                if (d.canGC()) {
897                    list.add(d);
898                    if (maxPurgedDests > 0 && list.size() == maxPurgedDests) {
899                        break;
900                    }
901                }
902            }
903
904            if (!list.isEmpty()) {
905                ConnectionContext context = BrokerSupport.getConnectionContext(this);
906                context.setBroker(this);
907
908                for (Destination dest : list) {
909                    Logger log = LOG;
910                    if (dest instanceof BaseDestination) {
911                        log = ((BaseDestination) dest).getLog();
912                    }
913                    log.info("{} Inactive for longer than {} ms - removing ...", dest.getName(), dest.getInactiveTimeoutBeforeGC());
914                    try {
915                        getRoot().removeDestination(context, dest.getActiveMQDestination(), isAllowTempAutoCreationOnSend() ? 1 : 0);
916                    } catch (Throwable e) {
917                        LOG.error("Failed to remove inactive destination {}", dest, e);
918                    }
919                }
920            }
921        } finally {
922            inactiveDestinationsPurgeLock.writeLock().unlock();
923        }
924    }
925
926    public boolean isAllowTempAutoCreationOnSend() {
927        return allowTempAutoCreationOnSend;
928    }
929
930    public void setAllowTempAutoCreationOnSend(boolean allowTempAutoCreationOnSend) {
931        this.allowTempAutoCreationOnSend = allowTempAutoCreationOnSend;
932    }
933
934    @Override
935    public void reapplyInterceptor() {
936        queueRegion.reapplyInterceptor();
937        topicRegion.reapplyInterceptor();
938        tempQueueRegion.reapplyInterceptor();
939        tempTopicRegion.reapplyInterceptor();
940    }
941}