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.network;
018
019import java.io.IOException;
020import java.security.GeneralSecurityException;
021import java.security.cert.X509Certificate;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Properties;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.ConcurrentMap;
029import java.util.concurrent.CountDownLatch;
030import java.util.concurrent.ExecutionException;
031import java.util.concurrent.ExecutorService;
032import java.util.concurrent.Executors;
033import java.util.concurrent.Future;
034import java.util.concurrent.TimeUnit;
035import java.util.concurrent.TimeoutException;
036import java.util.concurrent.atomic.AtomicBoolean;
037import java.util.concurrent.atomic.AtomicLong;
038
039import javax.management.ObjectName;
040
041import org.apache.activemq.DestinationDoesNotExistException;
042import org.apache.activemq.Service;
043import org.apache.activemq.advisory.AdvisoryBroker;
044import org.apache.activemq.advisory.AdvisorySupport;
045import org.apache.activemq.broker.BrokerService;
046import org.apache.activemq.broker.BrokerServiceAware;
047import org.apache.activemq.broker.ConnectionContext;
048import org.apache.activemq.broker.TransportConnection;
049import org.apache.activemq.broker.region.AbstractRegion;
050import org.apache.activemq.broker.region.DurableTopicSubscription;
051import org.apache.activemq.broker.region.Region;
052import org.apache.activemq.broker.region.RegionBroker;
053import org.apache.activemq.broker.region.Subscription;
054import org.apache.activemq.broker.region.policy.PolicyEntry;
055import org.apache.activemq.command.ActiveMQDestination;
056import org.apache.activemq.command.ActiveMQMessage;
057import org.apache.activemq.command.ActiveMQTempDestination;
058import org.apache.activemq.command.ActiveMQTopic;
059import org.apache.activemq.command.BrokerId;
060import org.apache.activemq.command.BrokerInfo;
061import org.apache.activemq.command.Command;
062import org.apache.activemq.command.ConnectionError;
063import org.apache.activemq.command.ConnectionId;
064import org.apache.activemq.command.ConnectionInfo;
065import org.apache.activemq.command.ConsumerId;
066import org.apache.activemq.command.ConsumerInfo;
067import org.apache.activemq.command.DataStructure;
068import org.apache.activemq.command.DestinationInfo;
069import org.apache.activemq.command.ExceptionResponse;
070import org.apache.activemq.command.KeepAliveInfo;
071import org.apache.activemq.command.Message;
072import org.apache.activemq.command.MessageAck;
073import org.apache.activemq.command.MessageDispatch;
074import org.apache.activemq.command.MessageId;
075import org.apache.activemq.command.NetworkBridgeFilter;
076import org.apache.activemq.command.ProducerInfo;
077import org.apache.activemq.command.RemoveInfo;
078import org.apache.activemq.command.RemoveSubscriptionInfo;
079import org.apache.activemq.command.Response;
080import org.apache.activemq.command.SessionInfo;
081import org.apache.activemq.command.ShutdownInfo;
082import org.apache.activemq.command.SubscriptionInfo;
083import org.apache.activemq.command.WireFormatInfo;
084import org.apache.activemq.filter.DestinationFilter;
085import org.apache.activemq.filter.MessageEvaluationContext;
086import org.apache.activemq.security.SecurityContext;
087import org.apache.activemq.transport.DefaultTransportListener;
088import org.apache.activemq.transport.FutureResponse;
089import org.apache.activemq.transport.ResponseCallback;
090import org.apache.activemq.transport.Transport;
091import org.apache.activemq.transport.TransportDisposedIOException;
092import org.apache.activemq.transport.TransportFilter;
093import org.apache.activemq.transport.tcp.SslTransport;
094import org.apache.activemq.util.IdGenerator;
095import org.apache.activemq.util.IntrospectionSupport;
096import org.apache.activemq.util.LongSequenceGenerator;
097import org.apache.activemq.util.MarshallingSupport;
098import org.apache.activemq.util.ServiceStopper;
099import org.apache.activemq.util.ServiceSupport;
100import org.slf4j.Logger;
101import org.slf4j.LoggerFactory;
102
103/**
104 * A useful base class for implementing demand forwarding bridges.
105 */
106public abstract class DemandForwardingBridgeSupport implements NetworkBridge, BrokerServiceAware {
107    private static final Logger LOG = LoggerFactory.getLogger(DemandForwardingBridgeSupport.class);
108    protected static final String DURABLE_SUB_PREFIX = "NC-DS_";
109    protected final Transport localBroker;
110    protected final Transport remoteBroker;
111    protected IdGenerator idGenerator = new IdGenerator();
112    protected final LongSequenceGenerator consumerIdGenerator = new LongSequenceGenerator();
113    protected ConnectionInfo localConnectionInfo;
114    protected ConnectionInfo remoteConnectionInfo;
115    protected SessionInfo localSessionInfo;
116    protected ProducerInfo producerInfo;
117    protected String remoteBrokerName = "Unknown";
118    protected String localClientId;
119    protected ConsumerInfo demandConsumerInfo;
120    protected int demandConsumerDispatched;
121    protected final AtomicBoolean localBridgeStarted = new AtomicBoolean(false);
122    protected final AtomicBoolean remoteBridgeStarted = new AtomicBoolean(false);
123    protected final AtomicBoolean bridgeFailed = new AtomicBoolean();
124    protected final AtomicBoolean disposed = new AtomicBoolean();
125    protected BrokerId localBrokerId;
126    protected ActiveMQDestination[] excludedDestinations;
127    protected ActiveMQDestination[] dynamicallyIncludedDestinations;
128    protected ActiveMQDestination[] staticallyIncludedDestinations;
129    protected ActiveMQDestination[] durableDestinations;
130    protected final ConcurrentMap<ConsumerId, DemandSubscription> subscriptionMapByLocalId = new ConcurrentHashMap<ConsumerId, DemandSubscription>();
131    protected final ConcurrentMap<ConsumerId, DemandSubscription> subscriptionMapByRemoteId = new ConcurrentHashMap<ConsumerId, DemandSubscription>();
132    protected final BrokerId localBrokerPath[] = new BrokerId[]{null};
133    protected final CountDownLatch startedLatch = new CountDownLatch(2);
134    protected final CountDownLatch localStartedLatch = new CountDownLatch(1);
135    protected final AtomicBoolean lastConnectSucceeded = new AtomicBoolean(false);
136    protected NetworkBridgeConfiguration configuration;
137    protected final NetworkBridgeFilterFactory defaultFilterFactory = new DefaultNetworkBridgeFilterFactory();
138
139    protected final BrokerId remoteBrokerPath[] = new BrokerId[]{null};
140    protected BrokerId remoteBrokerId;
141
142    final AtomicLong enqueueCounter = new AtomicLong();
143    final AtomicLong dequeueCounter = new AtomicLong();
144
145    private NetworkBridgeListener networkBridgeListener;
146    private boolean createdByDuplex;
147    private BrokerInfo localBrokerInfo;
148    private BrokerInfo remoteBrokerInfo;
149
150    private final FutureBrokerInfo futureRemoteBrokerInfo = new FutureBrokerInfo(remoteBrokerInfo, disposed);
151    private final FutureBrokerInfo futureLocalBrokerInfo = new FutureBrokerInfo(localBrokerInfo, disposed);
152
153    private final AtomicBoolean started = new AtomicBoolean();
154    private TransportConnection duplexInitiatingConnection;
155    private final AtomicBoolean duplexInitiatingConnectionInfoReceived = new AtomicBoolean();
156    protected BrokerService brokerService = null;
157    private ObjectName mbeanObjectName;
158    private final ExecutorService serialExecutor = Executors.newSingleThreadExecutor();
159    private Transport duplexInboundLocalBroker = null;
160    private ProducerInfo duplexInboundLocalProducerInfo;
161
162    public DemandForwardingBridgeSupport(NetworkBridgeConfiguration configuration, Transport localBroker, Transport remoteBroker) {
163        this.configuration = configuration;
164        this.localBroker = localBroker;
165        this.remoteBroker = remoteBroker;
166    }
167
168    public void duplexStart(TransportConnection connection, BrokerInfo localBrokerInfo, BrokerInfo remoteBrokerInfo) throws Exception {
169        this.localBrokerInfo = localBrokerInfo;
170        this.remoteBrokerInfo = remoteBrokerInfo;
171        this.duplexInitiatingConnection = connection;
172        start();
173        serviceRemoteCommand(remoteBrokerInfo);
174    }
175
176    @Override
177    public void start() throws Exception {
178        if (started.compareAndSet(false, true)) {
179
180            if (brokerService == null) {
181                throw new IllegalArgumentException("BrokerService is null on " + this);
182            }
183
184            if (isDuplex()) {
185                duplexInboundLocalBroker = NetworkBridgeFactory.createLocalTransport(brokerService.getBroker());
186                duplexInboundLocalBroker.setTransportListener(new DefaultTransportListener() {
187
188                    @Override
189                    public void onCommand(Object o) {
190                        Command command = (Command) o;
191                        serviceLocalCommand(command);
192                    }
193
194                    @Override
195                    public void onException(IOException error) {
196                        serviceLocalException(error);
197                    }
198                });
199                duplexInboundLocalBroker.start();
200            }
201
202            localBroker.setTransportListener(new DefaultTransportListener() {
203
204                @Override
205                public void onCommand(Object o) {
206                    Command command = (Command) o;
207                    serviceLocalCommand(command);
208                }
209
210                @Override
211                public void onException(IOException error) {
212                    if (!futureLocalBrokerInfo.isDone()) {
213                        LOG.info("error with pending local brokerInfo on: " + localBroker, error);
214                        futureLocalBrokerInfo.cancel(true);
215                        return;
216                    }
217                    serviceLocalException(error);
218                }
219            });
220
221            remoteBroker.setTransportListener(new DefaultTransportListener() {
222
223                @Override
224                public void onCommand(Object o) {
225                    Command command = (Command) o;
226                    serviceRemoteCommand(command);
227                }
228
229                @Override
230                public void onException(IOException error) {
231                    if (!futureRemoteBrokerInfo.isDone()) {
232                        LOG.info("error with pending remote brokerInfo on: " + remoteBroker, error);
233                        futureRemoteBrokerInfo.cancel(true);
234                        return;
235                    }
236                    serviceRemoteException(error);
237                }
238            });
239
240            remoteBroker.start();
241            localBroker.start();
242
243            if (!disposed.get()) {
244                try {
245                    triggerStartAsyncNetworkBridgeCreation();
246                } catch (IOException e) {
247                    LOG.warn("Caught exception from remote start", e);
248                }
249            } else {
250                LOG.warn("Bridge was disposed before the start() method was fully executed.");
251                throw new TransportDisposedIOException();
252            }
253        }
254    }
255
256    @Override
257    public void stop() throws Exception {
258        if (started.compareAndSet(true, false)) {
259            if (disposed.compareAndSet(false, true)) {
260                LOG.debug(" stopping {} bridge to {}", configuration.getBrokerName(), remoteBrokerName);
261
262                futureRemoteBrokerInfo.cancel(true);
263                futureLocalBrokerInfo.cancel(true);
264
265                NetworkBridgeListener l = this.networkBridgeListener;
266                if (l != null) {
267                    l.onStop(this);
268                }
269                try {
270                    // local start complete
271                    if (startedLatch.getCount() < 2) {
272                        LOG.trace("{} unregister bridge ({}) to {}", new Object[]{
273                                configuration.getBrokerName(), this, remoteBrokerName
274                        });
275                        brokerService.getBroker().removeBroker(null, remoteBrokerInfo);
276                        brokerService.getBroker().networkBridgeStopped(remoteBrokerInfo);
277                    }
278
279                    remoteBridgeStarted.set(false);
280                    final CountDownLatch sendShutdown = new CountDownLatch(1);
281
282                    brokerService.getTaskRunnerFactory().execute(new Runnable() {
283                        @Override
284                        public void run() {
285                            try {
286                                serialExecutor.shutdown();
287                                if (!serialExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
288                                    List<Runnable> pendingTasks = serialExecutor.shutdownNow();
289                                    LOG.info("pending tasks on stop {}", pendingTasks);
290                                }
291                                localBroker.oneway(new ShutdownInfo());
292                                remoteBroker.oneway(new ShutdownInfo());
293                            } catch (Throwable e) {
294                                LOG.debug("Caught exception sending shutdown", e);
295                            } finally {
296                                sendShutdown.countDown();
297                            }
298
299                        }
300                    }, "ActiveMQ ForwardingBridge StopTask");
301
302                    if (!sendShutdown.await(10, TimeUnit.SECONDS)) {
303                        LOG.info("Network Could not shutdown in a timely manner");
304                    }
305                } finally {
306                    ServiceStopper ss = new ServiceStopper();
307                    ss.stop(remoteBroker);
308                    ss.stop(localBroker);
309                    ss.stop(duplexInboundLocalBroker);
310                    // Release the started Latch since another thread could be
311                    // stuck waiting for it to start up.
312                    startedLatch.countDown();
313                    startedLatch.countDown();
314                    localStartedLatch.countDown();
315
316                    ss.throwFirstException();
317                }
318            }
319
320            LOG.info("{} bridge to {} stopped", configuration.getBrokerName(), remoteBrokerName);
321        }
322    }
323
324    protected void triggerStartAsyncNetworkBridgeCreation() throws IOException {
325        brokerService.getTaskRunnerFactory().execute(new Runnable() {
326            @Override
327            public void run() {
328                final String originalName = Thread.currentThread().getName();
329                Thread.currentThread().setName("triggerStartAsyncNetworkBridgeCreation: " +
330                        "remoteBroker=" + remoteBroker + ", localBroker= " + localBroker);
331
332                try {
333                    // First we collect the info data from both the local and remote ends
334                    collectBrokerInfos();
335
336                    // Once we have all required broker info we can attempt to start
337                    // the local and then remote sides of the bridge.
338                    doStartLocalAndRemoteBridges();
339                } finally {
340                    Thread.currentThread().setName(originalName);
341                }
342            }
343        });
344    }
345
346    private void collectBrokerInfos() {
347
348        // First wait for the remote to feed us its BrokerInfo, then we can check on
349        // the LocalBrokerInfo and decide is this is a loop.
350        try {
351            remoteBrokerInfo = futureRemoteBrokerInfo.get();
352            if (remoteBrokerInfo == null) {
353                serviceLocalException(new Throwable("remoteBrokerInfo is null"));
354                return;
355            }
356        } catch (Exception e) {
357            serviceRemoteException(e);
358            return;
359        }
360
361        try {
362            localBrokerInfo = futureLocalBrokerInfo.get();
363            if (localBrokerInfo == null) {
364                serviceLocalException(new Throwable("localBrokerInfo is null"));
365                return;
366            }
367
368            // Before we try and build the bridge lets check if we are in a loop
369            // and if so just stop now before registering anything.
370            remoteBrokerId = remoteBrokerInfo.getBrokerId();
371            if (localBrokerId.equals(remoteBrokerId)) {
372                LOG.trace("{} disconnecting remote loop back connector for: {}, with id: {}", new Object[]{
373                        configuration.getBrokerName(), remoteBrokerName, remoteBrokerId
374                });
375                ServiceSupport.dispose(localBroker);
376                ServiceSupport.dispose(remoteBroker);
377                // the bridge is left in a bit of limbo, but it won't get retried
378                // in this state.
379                return;
380            }
381
382            // Fill in the remote broker's information now.
383            remoteBrokerPath[0] = remoteBrokerId;
384            remoteBrokerName = remoteBrokerInfo.getBrokerName();
385            if (configuration.isUseBrokerNamesAsIdSeed()) {
386                idGenerator = new IdGenerator(brokerService.getBrokerName() + "->" + remoteBrokerName);
387            }
388        } catch (Throwable e) {
389            serviceLocalException(e);
390        }
391    }
392
393    private void doStartLocalAndRemoteBridges() {
394
395        if (disposed.get()) {
396            return;
397        }
398
399        if (isCreatedByDuplex()) {
400            // apply remote (propagated) configuration to local duplex bridge before start
401            Properties props = null;
402            try {
403                props = MarshallingSupport.stringToProperties(remoteBrokerInfo.getNetworkProperties());
404                IntrospectionSupport.getProperties(configuration, props, null);
405                if (configuration.getExcludedDestinations() != null) {
406                    excludedDestinations = configuration.getExcludedDestinations().toArray(
407                            new ActiveMQDestination[configuration.getExcludedDestinations().size()]);
408                }
409                if (configuration.getStaticallyIncludedDestinations() != null) {
410                    staticallyIncludedDestinations = configuration.getStaticallyIncludedDestinations().toArray(
411                            new ActiveMQDestination[configuration.getStaticallyIncludedDestinations().size()]);
412                }
413                if (configuration.getDynamicallyIncludedDestinations() != null) {
414                    dynamicallyIncludedDestinations = configuration.getDynamicallyIncludedDestinations().toArray(
415                            new ActiveMQDestination[configuration.getDynamicallyIncludedDestinations().size()]);
416                }
417            } catch (Throwable t) {
418                LOG.error("Error mapping remote configuration: {}", props, t);
419            }
420        }
421
422        try {
423            startLocalBridge();
424        } catch (Throwable e) {
425            serviceLocalException(e);
426            return;
427        }
428
429        try {
430            startRemoteBridge();
431        } catch (Throwable e) {
432            serviceRemoteException(e);
433            return;
434        }
435
436        try {
437            if (safeWaitUntilStarted()) {
438                setupStaticDestinations();
439            }
440        } catch (Throwable e) {
441            serviceLocalException(e);
442        }
443    }
444
445    private void startLocalBridge() throws Throwable {
446        if (!bridgeFailed.get() && localBridgeStarted.compareAndSet(false, true)) {
447            synchronized (this) {
448                LOG.trace("{} starting local Bridge, localBroker={}", configuration.getBrokerName(), localBroker);
449                if (!disposed.get()) {
450
451                    if (idGenerator == null) {
452                        throw new IllegalStateException("Id Generator cannot be null");
453                    }
454
455                    localConnectionInfo = new ConnectionInfo();
456                    localConnectionInfo.setConnectionId(new ConnectionId(idGenerator.generateId()));
457                    localClientId = configuration.getName() + "_" + remoteBrokerName + "_inbound_" + configuration.getBrokerName();
458                    localConnectionInfo.setClientId(localClientId);
459                    localConnectionInfo.setUserName(configuration.getUserName());
460                    localConnectionInfo.setPassword(configuration.getPassword());
461                    Transport originalTransport = remoteBroker;
462                    while (originalTransport instanceof TransportFilter) {
463                        originalTransport = ((TransportFilter) originalTransport).getNext();
464                    }
465                    if (originalTransport instanceof SslTransport) {
466                        X509Certificate[] peerCerts = ((SslTransport) originalTransport).getPeerCertificates();
467                        localConnectionInfo.setTransportContext(peerCerts);
468                    }
469                    // sync requests that may fail
470                    Object resp = localBroker.request(localConnectionInfo);
471                    if (resp instanceof ExceptionResponse) {
472                        throw ((ExceptionResponse) resp).getException();
473                    }
474                    localSessionInfo = new SessionInfo(localConnectionInfo, 1);
475                    localBroker.oneway(localSessionInfo);
476
477                    if (configuration.isDuplex()) {
478                        // separate in-bound channel for forwards so we don't
479                        // contend with out-bound dispatch on same connection
480                        ConnectionInfo duplexLocalConnectionInfo = new ConnectionInfo();
481                        duplexLocalConnectionInfo.setConnectionId(new ConnectionId(idGenerator.generateId()));
482                        duplexLocalConnectionInfo.setClientId(configuration.getName() + "_" + remoteBrokerName + "_inbound_duplex_"
483                                + configuration.getBrokerName());
484                        duplexLocalConnectionInfo.setUserName(configuration.getUserName());
485                        duplexLocalConnectionInfo.setPassword(configuration.getPassword());
486
487                        if (originalTransport instanceof SslTransport) {
488                            X509Certificate[] peerCerts = ((SslTransport) originalTransport).getPeerCertificates();
489                            duplexLocalConnectionInfo.setTransportContext(peerCerts);
490                        }
491                        // sync requests that may fail
492                        resp = duplexInboundLocalBroker.request(duplexLocalConnectionInfo);
493                        if (resp instanceof ExceptionResponse) {
494                            throw ((ExceptionResponse) resp).getException();
495                        }
496                        SessionInfo duplexInboundSession = new SessionInfo(duplexLocalConnectionInfo, 1);
497                        duplexInboundLocalProducerInfo = new ProducerInfo(duplexInboundSession, 1);
498                        duplexInboundLocalBroker.oneway(duplexInboundSession);
499                        duplexInboundLocalBroker.oneway(duplexInboundLocalProducerInfo);
500                    }
501                    brokerService.getBroker().networkBridgeStarted(remoteBrokerInfo, this.createdByDuplex, remoteBroker.toString());
502                    NetworkBridgeListener l = this.networkBridgeListener;
503                    if (l != null) {
504                        l.onStart(this);
505                    }
506
507                    // Let the local broker know the remote broker's ID.
508                    localBroker.oneway(remoteBrokerInfo);
509                    // new peer broker (a consumer can work with remote broker also)
510                    brokerService.getBroker().addBroker(null, remoteBrokerInfo);
511
512                    LOG.info("Network connection between {} and {} ({}) has been established.", new Object[]{
513                            localBroker, remoteBroker, remoteBrokerName
514                    });
515                    LOG.trace("{} register bridge ({}) to {}", new Object[]{
516                            configuration.getBrokerName(), this, remoteBrokerName
517                    });
518                } else {
519                    LOG.warn("Bridge was disposed before the startLocalBridge() method was fully executed.");
520                }
521                startedLatch.countDown();
522                localStartedLatch.countDown();
523            }
524        }
525    }
526
527    protected void startRemoteBridge() throws Exception {
528        if (!bridgeFailed.get() && remoteBridgeStarted.compareAndSet(false, true)) {
529            LOG.trace("{} starting remote Bridge, remoteBroker={}", configuration.getBrokerName(), remoteBroker);
530            synchronized (this) {
531                if (!isCreatedByDuplex()) {
532                    BrokerInfo brokerInfo = new BrokerInfo();
533                    brokerInfo.setBrokerName(configuration.getBrokerName());
534                    brokerInfo.setBrokerURL(configuration.getBrokerURL());
535                    brokerInfo.setNetworkConnection(true);
536                    brokerInfo.setDuplexConnection(configuration.isDuplex());
537                    // set our properties
538                    Properties props = new Properties();
539                    IntrospectionSupport.getProperties(configuration, props, null);
540                    props.remove("networkTTL");
541                    String str = MarshallingSupport.propertiesToString(props);
542                    brokerInfo.setNetworkProperties(str);
543                    brokerInfo.setBrokerId(this.localBrokerId);
544                    remoteBroker.oneway(brokerInfo);
545                }
546                if (remoteConnectionInfo != null) {
547                    remoteBroker.oneway(remoteConnectionInfo.createRemoveCommand());
548                }
549                remoteConnectionInfo = new ConnectionInfo();
550                remoteConnectionInfo.setConnectionId(new ConnectionId(idGenerator.generateId()));
551                remoteConnectionInfo.setClientId(configuration.getName() + "_" + configuration.getBrokerName() + "_outbound");
552                remoteConnectionInfo.setUserName(configuration.getUserName());
553                remoteConnectionInfo.setPassword(configuration.getPassword());
554                remoteBroker.oneway(remoteConnectionInfo);
555
556                SessionInfo remoteSessionInfo = new SessionInfo(remoteConnectionInfo, 1);
557                remoteBroker.oneway(remoteSessionInfo);
558                producerInfo = new ProducerInfo(remoteSessionInfo, 1);
559                producerInfo.setResponseRequired(false);
560                remoteBroker.oneway(producerInfo);
561                // Listen to consumer advisory messages on the remote broker to determine demand.
562                if (!configuration.isStaticBridge()) {
563                    demandConsumerInfo = new ConsumerInfo(remoteSessionInfo, 1);
564                    // always dispatch advisory message asynchronously so that
565                    // we never block the producer broker if we are slow
566                    demandConsumerInfo.setDispatchAsync(true);
567                    String advisoryTopic = configuration.getDestinationFilter();
568                    if (configuration.isBridgeTempDestinations()) {
569                        advisoryTopic += "," + AdvisorySupport.TEMP_DESTINATION_COMPOSITE_ADVISORY_TOPIC;
570                    }
571                    demandConsumerInfo.setDestination(new ActiveMQTopic(advisoryTopic));
572                    demandConsumerInfo.setPrefetchSize(configuration.getPrefetchSize());
573                    remoteBroker.oneway(demandConsumerInfo);
574                }
575                startedLatch.countDown();
576            }
577        }
578    }
579
580    @Override
581    public void serviceRemoteException(Throwable error) {
582        if (!disposed.get()) {
583            if (error instanceof SecurityException || error instanceof GeneralSecurityException) {
584                LOG.error("Network connection between {} and {} shutdown due to a remote error: {}", new Object[]{
585                        localBroker, remoteBroker, error
586                });
587            } else {
588                LOG.warn("Network connection between {} and {} shutdown due to a remote error: {}", new Object[]{
589                        localBroker, remoteBroker, error
590                });
591            }
592            LOG.debug("The remote Exception was: {}", error, error);
593            brokerService.getTaskRunnerFactory().execute(new Runnable() {
594                @Override
595                public void run() {
596                    ServiceSupport.dispose(getControllingService());
597                }
598            });
599            fireBridgeFailed(error);
600        }
601    }
602
603    protected void serviceRemoteCommand(Command command) {
604        if (!disposed.get()) {
605            try {
606                if (command.isMessageDispatch()) {
607                    safeWaitUntilStarted();
608                    MessageDispatch md = (MessageDispatch) command;
609                    serviceRemoteConsumerAdvisory(md.getMessage().getDataStructure());
610                    ackAdvisory(md.getMessage());
611                } else if (command.isBrokerInfo()) {
612                    futureRemoteBrokerInfo.set((BrokerInfo) command);
613                } else if (command.getClass() == ConnectionError.class) {
614                    ConnectionError ce = (ConnectionError) command;
615                    serviceRemoteException(ce.getException());
616                } else {
617                    if (isDuplex()) {
618                        LOG.trace("{} duplex command type: {}", configuration.getBrokerName(), command.getDataStructureType());
619                        if (command.isMessage()) {
620                            final ActiveMQMessage message = (ActiveMQMessage) command;
621                            if (NetworkBridgeFilter.isAdvisoryInterpretedByNetworkBridge(message)) {
622                                serviceRemoteConsumerAdvisory(message.getDataStructure());
623                                ackAdvisory(message);
624                            } else {
625                                if (!isPermissableDestination(message.getDestination(), true)) {
626                                    return;
627                                }
628                                // message being forwarded - we need to
629                                // propagate the response to our local send
630                                if (canDuplexDispatch(message)) {
631                                    message.setProducerId(duplexInboundLocalProducerInfo.getProducerId());
632                                    if (message.isResponseRequired() || configuration.isAlwaysSyncSend()) {
633                                        duplexInboundLocalBroker.asyncRequest(message, new ResponseCallback() {
634                                            final int correlationId = message.getCommandId();
635
636                                            @Override
637                                            public void onCompletion(FutureResponse resp) {
638                                                try {
639                                                    Response reply = resp.getResult();
640                                                    reply.setCorrelationId(correlationId);
641                                                    remoteBroker.oneway(reply);
642                                                } catch (IOException error) {
643                                                    LOG.error("Exception: {} on duplex forward of: {}", error, message);
644                                                    serviceRemoteException(error);
645                                                }
646                                            }
647                                        });
648                                    } else {
649                                        duplexInboundLocalBroker.oneway(message);
650                                    }
651                                    serviceInboundMessage(message);
652                                } else {
653                                    if (message.isResponseRequired() || configuration.isAlwaysSyncSend()) {
654                                        Response reply = new Response();
655                                        reply.setCorrelationId(message.getCommandId());
656                                        remoteBroker.oneway(reply);
657                                    }
658                                }
659                            }
660                        } else {
661                            switch (command.getDataStructureType()) {
662                                case ConnectionInfo.DATA_STRUCTURE_TYPE:
663                                    if (duplexInitiatingConnection != null && duplexInitiatingConnectionInfoReceived.compareAndSet(false, true)) {
664                                        // end of initiating connection setup - propogate to initial connection to get mbean by clientid
665                                        duplexInitiatingConnection.processAddConnection((ConnectionInfo) command);
666                                    } else {
667                                        localBroker.oneway(command);
668                                    }
669                                    break;
670                                case SessionInfo.DATA_STRUCTURE_TYPE:
671                                    localBroker.oneway(command);
672                                    break;
673                                case ProducerInfo.DATA_STRUCTURE_TYPE:
674                                    // using duplexInboundLocalProducerInfo
675                                    break;
676                                case MessageAck.DATA_STRUCTURE_TYPE:
677                                    MessageAck ack = (MessageAck) command;
678                                    DemandSubscription localSub = subscriptionMapByRemoteId.get(ack.getConsumerId());
679                                    if (localSub != null) {
680                                        ack.setConsumerId(localSub.getLocalInfo().getConsumerId());
681                                        localBroker.oneway(ack);
682                                    } else {
683                                        LOG.warn("Matching local subscription not found for ack: {}", ack);
684                                    }
685                                    break;
686                                case ConsumerInfo.DATA_STRUCTURE_TYPE:
687                                    localStartedLatch.await();
688                                    if (started.get()) {
689                                        addConsumerInfo((ConsumerInfo) command);
690                                    } else {
691                                        // received a subscription whilst stopping
692                                        LOG.warn("Stopping - ignoring ConsumerInfo: {}", command);
693                                    }
694                                    break;
695                                case ShutdownInfo.DATA_STRUCTURE_TYPE:
696                                    // initiator is shutting down, controlled case
697                                    // abortive close dealt with by inactivity monitor
698                                    LOG.info("Stopping network bridge on shutdown of remote broker");
699                                    serviceRemoteException(new IOException(command.toString()));
700                                    break;
701                                default:
702                                    LOG.debug("Ignoring remote command: {}", command);
703                            }
704                        }
705                    } else {
706                        switch (command.getDataStructureType()) {
707                            case KeepAliveInfo.DATA_STRUCTURE_TYPE:
708                            case WireFormatInfo.DATA_STRUCTURE_TYPE:
709                            case ShutdownInfo.DATA_STRUCTURE_TYPE:
710                                break;
711                            default:
712                                LOG.warn("Unexpected remote command: {}", command);
713                        }
714                    }
715                }
716            } catch (Throwable e) {
717                LOG.debug("Exception processing remote command: {}", command, e);
718                serviceRemoteException(e);
719            }
720        }
721    }
722
723    private void ackAdvisory(Message message) throws IOException {
724        demandConsumerDispatched++;
725        if (demandConsumerDispatched > (demandConsumerInfo.getPrefetchSize() * .75)) {
726            MessageAck ack = new MessageAck(message, MessageAck.STANDARD_ACK_TYPE, demandConsumerDispatched);
727            ack.setConsumerId(demandConsumerInfo.getConsumerId());
728            remoteBroker.oneway(ack);
729            demandConsumerDispatched = 0;
730        }
731    }
732
733    private void serviceRemoteConsumerAdvisory(DataStructure data) throws IOException {
734        final int networkTTL = configuration.getConsumerTTL();
735        if (data.getClass() == ConsumerInfo.class) {
736            // Create a new local subscription
737            ConsumerInfo info = (ConsumerInfo) data;
738            BrokerId[] path = info.getBrokerPath();
739
740            if (info.isBrowser()) {
741                LOG.debug("{} Ignoring sub from {}, browsers explicitly suppressed", configuration.getBrokerName(), remoteBrokerName);
742                return;
743            }
744
745            if (path != null && networkTTL > -1 && path.length >= networkTTL) {
746                LOG.debug("{} Ignoring sub from {}, restricted to {} network hops only: {}", new Object[]{
747                        configuration.getBrokerName(), remoteBrokerName, networkTTL, info
748                });
749                return;
750            }
751
752            if (contains(path, localBrokerPath[0])) {
753                // Ignore this consumer as it's a consumer we locally sent to the broker.
754                LOG.debug("{} Ignoring sub from {}, already routed through this broker once: {}", new Object[]{
755                        configuration.getBrokerName(), remoteBrokerName, info
756                });
757                return;
758            }
759
760            if (!isPermissableDestination(info.getDestination())) {
761                // ignore if not in the permitted or in the excluded list
762                LOG.debug("{} Ignoring sub from {}, destination {} is not permitted: {}", new Object[]{
763                        configuration.getBrokerName(), remoteBrokerName, info.getDestination(), info
764                });
765                return;
766            }
767
768            // in a cyclic network there can be multiple bridges per broker that can propagate
769            // a network subscription so there is a need to synchronize on a shared entity
770            synchronized (brokerService.getVmConnectorURI()) {
771                addConsumerInfo(info);
772            }
773        } else if (data.getClass() == DestinationInfo.class) {
774            // It's a destination info - we want to pass up information about temporary destinations
775            final DestinationInfo destInfo = (DestinationInfo) data;
776            BrokerId[] path = destInfo.getBrokerPath();
777            if (path != null && networkTTL > -1 && path.length >= networkTTL) {
778                LOG.debug("{} Ignoring destination {} restricted to {} network hops only", new Object[]{
779                        configuration.getBrokerName(), destInfo, networkTTL
780                });
781                return;
782            }
783            if (contains(destInfo.getBrokerPath(), localBrokerPath[0])) {
784                LOG.debug("{} Ignoring destination {} already routed through this broker once", configuration.getBrokerName(), destInfo);
785                return;
786            }
787            destInfo.setConnectionId(localConnectionInfo.getConnectionId());
788            if (destInfo.getDestination() instanceof ActiveMQTempDestination) {
789                // re-set connection id so comes from here
790                ActiveMQTempDestination tempDest = (ActiveMQTempDestination) destInfo.getDestination();
791                tempDest.setConnectionId(localSessionInfo.getSessionId().getConnectionId());
792            }
793            destInfo.setBrokerPath(appendToBrokerPath(destInfo.getBrokerPath(), getRemoteBrokerPath()));
794            LOG.trace("{} bridging {} destination on {} from {}, destination: {}", new Object[]{
795                    configuration.getBrokerName(), (destInfo.isAddOperation() ? "add" : "remove"), localBroker, remoteBrokerName, destInfo
796            });
797            if (destInfo.isRemoveOperation()) {
798                // Serialize with removeSub operations such that all removeSub advisories
799                // are generated
800                serialExecutor.execute(new Runnable() {
801                    @Override
802                    public void run() {
803                        try {
804                            localBroker.oneway(destInfo);
805                        } catch (IOException e) {
806                            LOG.warn("failed to deliver remove command for destination: {}", destInfo.getDestination(), e);
807                        }
808                    }
809                });
810            } else {
811                localBroker.oneway(destInfo);
812            }
813        } else if (data.getClass() == RemoveInfo.class) {
814            ConsumerId id = (ConsumerId) ((RemoveInfo) data).getObjectId();
815            removeDemandSubscription(id);
816        } else if (data.getClass() == RemoveSubscriptionInfo.class) {
817            RemoveSubscriptionInfo info = ((RemoveSubscriptionInfo) data);
818            SubscriptionInfo subscriptionInfo = new SubscriptionInfo(info.getClientId(), info.getSubscriptionName());
819            for (Iterator<DemandSubscription> i = subscriptionMapByLocalId.values().iterator(); i.hasNext(); ) {
820                DemandSubscription ds = i.next();
821                boolean removed = ds.getDurableRemoteSubs().remove(subscriptionInfo);
822                if (removed) {
823                    if (ds.getDurableRemoteSubs().isEmpty()) {
824
825                        // deactivate subscriber
826                        RemoveInfo removeInfo = new RemoveInfo(ds.getLocalInfo().getConsumerId());
827                        localBroker.oneway(removeInfo);
828
829                        // remove subscriber
830                        RemoveSubscriptionInfo sending = new RemoveSubscriptionInfo();
831                        sending.setClientId(localClientId);
832                        sending.setSubscriptionName(ds.getLocalDurableSubscriber().getSubscriptionName());
833                        sending.setConnectionId(this.localConnectionInfo.getConnectionId());
834                        localBroker.oneway(sending);
835                    }
836                }
837            }
838        }
839    }
840
841    @Override
842    public void serviceLocalException(Throwable error) {
843        serviceLocalException(null, error);
844    }
845
846    public void serviceLocalException(MessageDispatch messageDispatch, Throwable error) {
847        LOG.trace("serviceLocalException: disposed {} ex", disposed.get(), error);
848        if (!disposed.get()) {
849            if (error instanceof DestinationDoesNotExistException && ((DestinationDoesNotExistException) error).isTemporary()) {
850                // not a reason to terminate the bridge - temps can disappear with
851                // pending sends as the demand sub may outlive the remote dest
852                if (messageDispatch != null) {
853                    LOG.warn("PoisonAck of {} on forwarding error: {}", messageDispatch.getMessage().getMessageId(), error);
854                    try {
855                        MessageAck poisonAck = new MessageAck(messageDispatch, MessageAck.POSION_ACK_TYPE, 1);
856                        poisonAck.setPoisonCause(error);
857                        localBroker.oneway(poisonAck);
858                    } catch (IOException ioe) {
859                        LOG.error("Failed to posion ack message following forward failure: ", ioe);
860                    }
861                    fireFailedForwardAdvisory(messageDispatch, error);
862                } else {
863                    LOG.warn("Ignoring exception on forwarding to non existent temp dest: ", error);
864                }
865                return;
866            }
867
868            LOG.info("Network connection between {} and {} shutdown due to a local error: {}", new Object[]{localBroker, remoteBroker, error});
869            LOG.debug("The local Exception was: {}", error, error);
870
871            brokerService.getTaskRunnerFactory().execute(new Runnable() {
872                @Override
873                public void run() {
874                    ServiceSupport.dispose(getControllingService());
875                }
876            });
877            fireBridgeFailed(error);
878        }
879    }
880
881    private void fireFailedForwardAdvisory(MessageDispatch messageDispatch, Throwable error) {
882        if (configuration.isAdvisoryForFailedForward()) {
883            AdvisoryBroker advisoryBroker = null;
884            try {
885                advisoryBroker = (AdvisoryBroker) brokerService.getBroker().getAdaptor(AdvisoryBroker.class);
886
887                if (advisoryBroker != null) {
888                    ConnectionContext context = new ConnectionContext();
889                    context.setSecurityContext(SecurityContext.BROKER_SECURITY_CONTEXT);
890                    context.setBroker(brokerService.getBroker());
891
892                    ActiveMQMessage advisoryMessage = new ActiveMQMessage();
893                    advisoryMessage.setStringProperty("cause", error.getLocalizedMessage());
894                    advisoryBroker.fireAdvisory(context, AdvisorySupport.getNetworkBridgeForwardFailureAdvisoryTopic(), messageDispatch.getMessage(), null,
895                            advisoryMessage);
896
897                }
898            } catch (Exception e) {
899                LOG.warn("failed to fire forward failure advisory, cause: {}", e);
900                LOG.debug("detail", e);
901            }
902        }
903    }
904
905    protected Service getControllingService() {
906        return duplexInitiatingConnection != null ? duplexInitiatingConnection : DemandForwardingBridgeSupport.this;
907    }
908
909    protected void addSubscription(DemandSubscription sub) throws IOException {
910        if (sub != null) {
911            if (isDuplex()) {
912                // async vm transport, need to wait for completion
913                localBroker.request(sub.getLocalInfo());
914            } else {
915                localBroker.oneway(sub.getLocalInfo());
916            }
917        }
918    }
919
920    protected void removeSubscription(final DemandSubscription sub) throws IOException {
921        if (sub != null) {
922            LOG.trace("{} remove local subscription: {} for remote {}", new Object[]{configuration.getBrokerName(), sub.getLocalInfo().getConsumerId(), sub.getRemoteInfo().getConsumerId()});
923
924            // ensure not available for conduit subs pending removal
925            subscriptionMapByLocalId.remove(sub.getLocalInfo().getConsumerId());
926            subscriptionMapByRemoteId.remove(sub.getRemoteInfo().getConsumerId());
927
928            // continue removal in separate thread to free up this thread for outstanding responses
929            // Serialize with removeDestination operations so that removeSubs are serialized with
930            // removeDestinations such that all removeSub advisories are generated
931            serialExecutor.execute(new Runnable() {
932                @Override
933                public void run() {
934                    sub.waitForCompletion();
935                    try {
936                        localBroker.oneway(sub.getLocalInfo().createRemoveCommand());
937                    } catch (IOException e) {
938                        LOG.warn("failed to deliver remove command for local subscription, for remote {}", sub.getRemoteInfo().getConsumerId(), e);
939                    }
940                }
941            });
942        }
943    }
944
945    protected Message configureMessage(MessageDispatch md) throws IOException {
946        Message message = md.getMessage().copy();
947        // Update the packet to show where it came from.
948        message.setBrokerPath(appendToBrokerPath(message.getBrokerPath(), localBrokerPath));
949        message.setProducerId(producerInfo.getProducerId());
950        message.setDestination(md.getDestination());
951        message.setMemoryUsage(null);
952        if (message.getOriginalTransactionId() == null) {
953            message.setOriginalTransactionId(message.getTransactionId());
954        }
955        message.setTransactionId(null);
956        if (configuration.isUseCompression()) {
957            message.compress();
958        }
959        return message;
960    }
961
962    protected void serviceLocalCommand(Command command) {
963        if (!disposed.get()) {
964            try {
965                if (command.isMessageDispatch()) {
966                    safeWaitUntilStarted();
967                    enqueueCounter.incrementAndGet();
968                    final MessageDispatch md = (MessageDispatch) command;
969                    final DemandSubscription sub = subscriptionMapByLocalId.get(md.getConsumerId());
970                    if (sub != null && md.getMessage() != null && sub.incrementOutstandingResponses()) {
971
972                        if (suppressMessageDispatch(md, sub)) {
973                            LOG.debug("{} message not forwarded to {} because message came from there or fails TTL, brokerPath: {}, message: {}", new Object[]{
974                                    configuration.getBrokerName(), remoteBrokerName, Arrays.toString(md.getMessage().getBrokerPath()), md.getMessage()
975                            });
976                            // still ack as it may be durable
977                            try {
978                                localBroker.oneway(new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1));
979                            } finally {
980                                sub.decrementOutstandingResponses();
981                            }
982                            return;
983                        }
984
985                        Message message = configureMessage(md);
986                        LOG.debug("bridging ({} -> {}), consumer: {}, destination: {}, brokerPath: {}, message: {}", new Object[]{
987                                configuration.getBrokerName(), remoteBrokerName, (LOG.isTraceEnabled() ? message : message.getMessageId()), md.getConsumerId(), message.getDestination(), Arrays.toString(message.getBrokerPath()), message
988                        });
989
990                        if (isDuplex() && NetworkBridgeFilter.isAdvisoryInterpretedByNetworkBridge(message)) {
991                            try {
992                                // never request b/c they are eventually acked async
993                                remoteBroker.oneway(message);
994                            } finally {
995                                sub.decrementOutstandingResponses();
996                            }
997                            return;
998                        }
999
1000                        if (message.isPersistent() || configuration.isAlwaysSyncSend()) {
1001
1002                            // The message was not sent using async send, so we should only
1003                            // ack the local broker when we get confirmation that the remote
1004                            // broker has received the message.
1005                            remoteBroker.asyncRequest(message, new ResponseCallback() {
1006                                @Override
1007                                public void onCompletion(FutureResponse future) {
1008                                    try {
1009                                        Response response = future.getResult();
1010                                        if (response.isException()) {
1011                                            ExceptionResponse er = (ExceptionResponse) response;
1012                                            serviceLocalException(md, er.getException());
1013                                        } else {
1014                                            localBroker.oneway(new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1));
1015                                            dequeueCounter.incrementAndGet();
1016                                        }
1017                                    } catch (IOException e) {
1018                                        serviceLocalException(md, e);
1019                                    } finally {
1020                                        sub.decrementOutstandingResponses();
1021                                    }
1022                                }
1023                            });
1024
1025                        } else {
1026                            // If the message was originally sent using async send, we will
1027                            // preserve that QOS by bridging it using an async send (small chance
1028                            // of message loss).
1029                            try {
1030                                remoteBroker.oneway(message);
1031                                localBroker.oneway(new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1));
1032                                dequeueCounter.incrementAndGet();
1033                            } finally {
1034                                sub.decrementOutstandingResponses();
1035                            }
1036                        }
1037                        serviceOutbound(message);
1038                    } else {
1039                        LOG.debug("No subscription registered with this network bridge for consumerId: {} for message: {}", md.getConsumerId(), md.getMessage());
1040                    }
1041                } else if (command.isBrokerInfo()) {
1042                    futureLocalBrokerInfo.set((BrokerInfo) command);
1043                } else if (command.isShutdownInfo()) {
1044                    LOG.info("{} Shutting down {}", configuration.getBrokerName(), configuration.getName());
1045                    stop();
1046                } else if (command.getClass() == ConnectionError.class) {
1047                    ConnectionError ce = (ConnectionError) command;
1048                    serviceLocalException(ce.getException());
1049                } else {
1050                    switch (command.getDataStructureType()) {
1051                        case WireFormatInfo.DATA_STRUCTURE_TYPE:
1052                            break;
1053                        default:
1054                            LOG.warn("Unexpected local command: {}", command);
1055                    }
1056                }
1057            } catch (Throwable e) {
1058                LOG.warn("Caught an exception processing local command", e);
1059                serviceLocalException(e);
1060            }
1061        }
1062    }
1063
1064    private boolean suppressMessageDispatch(MessageDispatch md, DemandSubscription sub) throws Exception {
1065        boolean suppress = false;
1066        // for durable subs, suppression via filter leaves dangling acks so we
1067        // need to check here and allow the ack irrespective
1068        if (sub.getLocalInfo().isDurable()) {
1069            MessageEvaluationContext messageEvalContext = new MessageEvaluationContext();
1070            messageEvalContext.setMessageReference(md.getMessage());
1071            messageEvalContext.setDestination(md.getDestination());
1072            suppress = !sub.getNetworkBridgeFilter().matches(messageEvalContext);
1073            //AMQ-6465 - Need to decrement the reference count after checking matches() as
1074            //the call above will increment the reference count by 1
1075            messageEvalContext.getMessageReference().decrementReferenceCount();
1076        }
1077        return suppress;
1078    }
1079
1080    public static boolean contains(BrokerId[] brokerPath, BrokerId brokerId) {
1081        if (brokerPath != null) {
1082            for (BrokerId id : brokerPath) {
1083                if (brokerId.equals(id)) {
1084                    return true;
1085                }
1086            }
1087        }
1088        return false;
1089    }
1090
1091    protected BrokerId[] appendToBrokerPath(BrokerId[] brokerPath, BrokerId[] pathsToAppend) {
1092        if (brokerPath == null || brokerPath.length == 0) {
1093            return pathsToAppend;
1094        }
1095        BrokerId rc[] = new BrokerId[brokerPath.length + pathsToAppend.length];
1096        System.arraycopy(brokerPath, 0, rc, 0, brokerPath.length);
1097        System.arraycopy(pathsToAppend, 0, rc, brokerPath.length, pathsToAppend.length);
1098        return rc;
1099    }
1100
1101    protected BrokerId[] appendToBrokerPath(BrokerId[] brokerPath, BrokerId idToAppend) {
1102        if (brokerPath == null || brokerPath.length == 0) {
1103            return new BrokerId[]{idToAppend};
1104        }
1105        BrokerId rc[] = new BrokerId[brokerPath.length + 1];
1106        System.arraycopy(brokerPath, 0, rc, 0, brokerPath.length);
1107        rc[brokerPath.length] = idToAppend;
1108        return rc;
1109    }
1110
1111    protected boolean isPermissableDestination(ActiveMQDestination destination) {
1112        return isPermissableDestination(destination, false);
1113    }
1114
1115    protected boolean isPermissableDestination(ActiveMQDestination destination, boolean allowTemporary) {
1116        // Are we not bridging temporary destinations?
1117        if (destination.isTemporary()) {
1118            if (allowTemporary) {
1119                return true;
1120            } else {
1121                return configuration.isBridgeTempDestinations();
1122            }
1123        }
1124
1125        ActiveMQDestination[] dests = staticallyIncludedDestinations;
1126        if (dests != null && dests.length > 0) {
1127            for (ActiveMQDestination dest : dests) {
1128                DestinationFilter inclusionFilter = DestinationFilter.parseFilter(dest);
1129                if (dest != null && inclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) {
1130                    return true;
1131                }
1132            }
1133        }
1134
1135        dests = excludedDestinations;
1136        if (dests != null && dests.length > 0) {
1137            for (ActiveMQDestination dest : dests) {
1138                DestinationFilter exclusionFilter = DestinationFilter.parseFilter(dest);
1139                if (dest != null && exclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) {
1140                    return false;
1141                }
1142            }
1143        }
1144
1145        dests = dynamicallyIncludedDestinations;
1146        if (dests != null && dests.length > 0) {
1147            for (ActiveMQDestination dest : dests) {
1148                DestinationFilter inclusionFilter = DestinationFilter.parseFilter(dest);
1149                if (dest != null && inclusionFilter.matches(destination) && dest.getDestinationType() == destination.getDestinationType()) {
1150                    return true;
1151                }
1152            }
1153
1154            return false;
1155        }
1156        return true;
1157    }
1158
1159    /**
1160     * Subscriptions for these destinations are always created
1161     */
1162    protected void setupStaticDestinations() {
1163        ActiveMQDestination[] dests = staticallyIncludedDestinations;
1164        if (dests != null) {
1165            for (ActiveMQDestination dest : dests) {
1166                DemandSubscription sub = createDemandSubscription(dest);
1167                sub.setStaticallyIncluded(true);
1168                try {
1169                    addSubscription(sub);
1170                } catch (IOException e) {
1171                    LOG.error("Failed to add static destination {}", dest, e);
1172                }
1173                LOG.trace("{}, bridging messages for static destination: {}", configuration.getBrokerName(), dest);
1174            }
1175        }
1176    }
1177
1178    protected void addConsumerInfo(final ConsumerInfo consumerInfo) throws IOException {
1179        ConsumerInfo info = consumerInfo.copy();
1180        addRemoteBrokerToBrokerPath(info);
1181        DemandSubscription sub = createDemandSubscription(info);
1182        if (sub != null) {
1183            if (duplicateSuppressionIsRequired(sub)) {
1184                undoMapRegistration(sub);
1185            } else {
1186                if (consumerInfo.isDurable()) {
1187                    sub.getDurableRemoteSubs().add(new SubscriptionInfo(sub.getRemoteInfo().getClientId(), consumerInfo.getSubscriptionName()));
1188                }
1189                addSubscription(sub);
1190                LOG.debug("{} new demand subscription: {}", configuration.getBrokerName(), sub);
1191            }
1192        }
1193    }
1194
1195    private void undoMapRegistration(DemandSubscription sub) {
1196        subscriptionMapByLocalId.remove(sub.getLocalInfo().getConsumerId());
1197        subscriptionMapByRemoteId.remove(sub.getRemoteInfo().getConsumerId());
1198    }
1199
1200    /*
1201     * check our existing subs networkConsumerIds against the list of network
1202     * ids in this subscription A match means a duplicate which we suppress for
1203     * topics and maybe for queues
1204     */
1205    private boolean duplicateSuppressionIsRequired(DemandSubscription candidate) {
1206        final ConsumerInfo consumerInfo = candidate.getRemoteInfo();
1207        boolean suppress = false;
1208
1209        if (consumerInfo.getDestination().isQueue() && !configuration.isSuppressDuplicateQueueSubscriptions() || consumerInfo.getDestination().isTopic()
1210                && !configuration.isSuppressDuplicateTopicSubscriptions()) {
1211            return suppress;
1212        }
1213
1214        List<ConsumerId> candidateConsumers = consumerInfo.getNetworkConsumerIds();
1215        Collection<Subscription> currentSubs = getRegionSubscriptions(consumerInfo.getDestination());
1216        for (Subscription sub : currentSubs) {
1217            List<ConsumerId> networkConsumers = sub.getConsumerInfo().getNetworkConsumerIds();
1218            if (!networkConsumers.isEmpty()) {
1219                if (matchFound(candidateConsumers, networkConsumers)) {
1220                    if (isInActiveDurableSub(sub)) {
1221                        suppress = false;
1222                    } else {
1223                        suppress = hasLowerPriority(sub, candidate.getLocalInfo());
1224                    }
1225                    break;
1226                }
1227            }
1228        }
1229        return suppress;
1230    }
1231
1232    private boolean isInActiveDurableSub(Subscription sub) {
1233        return (sub.getConsumerInfo().isDurable() && sub instanceof DurableTopicSubscription && !((DurableTopicSubscription) sub).isActive());
1234    }
1235
1236    private boolean hasLowerPriority(Subscription existingSub, ConsumerInfo candidateInfo) {
1237        boolean suppress = false;
1238
1239        if (existingSub.getConsumerInfo().getPriority() >= candidateInfo.getPriority()) {
1240            LOG.debug("{} Ignoring duplicate subscription from {}, sub: {} is duplicate by network subscription with equal or higher network priority: {}, networkConsumerIds: {}", new Object[]{
1241                    configuration.getBrokerName(), remoteBrokerName, candidateInfo, existingSub, existingSub.getConsumerInfo().getNetworkConsumerIds()
1242            });
1243            suppress = true;
1244        } else {
1245            // remove the existing lower priority duplicate and allow this candidate
1246            try {
1247                removeDuplicateSubscription(existingSub);
1248
1249                LOG.debug("{} Replacing duplicate subscription {} with sub from {}, which has a higher priority, new sub: {}, networkConsumerIds: {}", new Object[]{
1250                        configuration.getBrokerName(), existingSub.getConsumerInfo(), remoteBrokerName, candidateInfo, candidateInfo.getNetworkConsumerIds()
1251                });
1252            } catch (IOException e) {
1253                LOG.error("Failed to remove duplicated sub as a result of sub with higher priority, sub: {}", existingSub, e);
1254            }
1255        }
1256        return suppress;
1257    }
1258
1259    private void removeDuplicateSubscription(Subscription existingSub) throws IOException {
1260        for (NetworkConnector connector : brokerService.getNetworkConnectors()) {
1261            if (connector.removeDemandSubscription(existingSub.getConsumerInfo().getConsumerId())) {
1262                break;
1263            }
1264        }
1265    }
1266
1267    private boolean matchFound(List<ConsumerId> candidateConsumers, List<ConsumerId> networkConsumers) {
1268        boolean found = false;
1269        for (ConsumerId aliasConsumer : networkConsumers) {
1270            if (candidateConsumers.contains(aliasConsumer)) {
1271                found = true;
1272                break;
1273            }
1274        }
1275        return found;
1276    }
1277
1278    protected final Collection<Subscription> getRegionSubscriptions(ActiveMQDestination dest) {
1279        RegionBroker region_broker = (RegionBroker) brokerService.getRegionBroker();
1280        Region region;
1281        Collection<Subscription> subs;
1282
1283        region = null;
1284        switch (dest.getDestinationType()) {
1285            case ActiveMQDestination.QUEUE_TYPE:
1286                region = region_broker.getQueueRegion();
1287                break;
1288            case ActiveMQDestination.TOPIC_TYPE:
1289                region = region_broker.getTopicRegion();
1290                break;
1291            case ActiveMQDestination.TEMP_QUEUE_TYPE:
1292                region = region_broker.getTempQueueRegion();
1293                break;
1294            case ActiveMQDestination.TEMP_TOPIC_TYPE:
1295                region = region_broker.getTempTopicRegion();
1296                break;
1297        }
1298
1299        if (region instanceof AbstractRegion) {
1300            subs = ((AbstractRegion) region).getSubscriptions().values();
1301        } else {
1302            subs = null;
1303        }
1304
1305        return subs;
1306    }
1307
1308    protected DemandSubscription createDemandSubscription(ConsumerInfo info) throws IOException {
1309        // add our original id to ourselves
1310        info.addNetworkConsumerId(info.getConsumerId());
1311        return doCreateDemandSubscription(info);
1312    }
1313
1314    protected DemandSubscription doCreateDemandSubscription(ConsumerInfo info) throws IOException {
1315        DemandSubscription result = new DemandSubscription(info);
1316        result.getLocalInfo().setConsumerId(new ConsumerId(localSessionInfo.getSessionId(), consumerIdGenerator.getNextSequenceId()));
1317        if (info.getDestination().isTemporary()) {
1318            // reset the local connection Id
1319            ActiveMQTempDestination dest = (ActiveMQTempDestination) result.getLocalInfo().getDestination();
1320            dest.setConnectionId(localConnectionInfo.getConnectionId().toString());
1321        }
1322
1323        if (configuration.isDecreaseNetworkConsumerPriority()) {
1324            byte priority = (byte) configuration.getConsumerPriorityBase();
1325            if (info.getBrokerPath() != null && info.getBrokerPath().length > 1) {
1326                // The longer the path to the consumer, the less it's consumer priority.
1327                priority -= info.getBrokerPath().length + 1;
1328            }
1329            result.getLocalInfo().setPriority(priority);
1330            LOG.debug("{} using priority: {} for subscription: {}", new Object[]{configuration.getBrokerName(), priority, info});
1331        }
1332        configureDemandSubscription(info, result);
1333        return result;
1334    }
1335
1336    final protected DemandSubscription createDemandSubscription(ActiveMQDestination destination) {
1337        ConsumerInfo info = new ConsumerInfo();
1338        info.setNetworkSubscription(true);
1339        info.setDestination(destination);
1340
1341        // Indicate that this subscription is being made on behalf of the remote broker.
1342        info.setBrokerPath(new BrokerId[]{remoteBrokerId});
1343
1344        // the remote info held by the DemandSubscription holds the original
1345        // consumerId, the local info get's overwritten
1346        info.setConsumerId(new ConsumerId(localSessionInfo.getSessionId(), consumerIdGenerator.getNextSequenceId()));
1347        DemandSubscription result = null;
1348        try {
1349            result = createDemandSubscription(info);
1350        } catch (IOException e) {
1351            LOG.error("Failed to create DemandSubscription ", e);
1352        }
1353        return result;
1354    }
1355
1356    protected void configureDemandSubscription(ConsumerInfo info, DemandSubscription sub) throws IOException {
1357        if (AdvisorySupport.isConsumerAdvisoryTopic(info.getDestination())) {
1358            sub.getLocalInfo().setDispatchAsync(true);
1359        } else {
1360            sub.getLocalInfo().setDispatchAsync(configuration.isDispatchAsync());
1361        }
1362        sub.getLocalInfo().setPrefetchSize(configuration.getPrefetchSize());
1363        subscriptionMapByLocalId.put(sub.getLocalInfo().getConsumerId(), sub);
1364        subscriptionMapByRemoteId.put(sub.getRemoteInfo().getConsumerId(), sub);
1365
1366        sub.setNetworkBridgeFilter(createNetworkBridgeFilter(info));
1367        if (!info.isDurable()) {
1368            // This works for now since we use a VM connection to the local broker.
1369            // may need to change if we ever subscribe to a remote broker.
1370            sub.getLocalInfo().setAdditionalPredicate(sub.getNetworkBridgeFilter());
1371        } else {
1372            sub.setLocalDurableSubscriber(new SubscriptionInfo(info.getClientId(), info.getSubscriptionName()));
1373        }
1374    }
1375
1376    protected void removeDemandSubscription(ConsumerId id) throws IOException {
1377        DemandSubscription sub = subscriptionMapByRemoteId.remove(id);
1378        LOG.debug("{} remove request on {} from {}, consumer id: {}, matching sub: {}", new Object[]{
1379                configuration.getBrokerName(), localBroker, remoteBrokerName, id, sub
1380        });
1381        if (sub != null) {
1382            removeSubscription(sub);
1383            LOG.debug("{} removed sub on {} from {}: {}", new Object[]{
1384                    configuration.getBrokerName(), localBroker, remoteBrokerName, sub.getRemoteInfo()
1385            });
1386        }
1387    }
1388
1389    protected boolean removeDemandSubscriptionByLocalId(ConsumerId consumerId) {
1390        boolean removeDone = false;
1391        DemandSubscription sub = subscriptionMapByLocalId.get(consumerId);
1392        if (sub != null) {
1393            try {
1394                removeDemandSubscription(sub.getRemoteInfo().getConsumerId());
1395                removeDone = true;
1396            } catch (IOException e) {
1397                LOG.debug("removeDemandSubscriptionByLocalId failed for localId: {}", consumerId, e);
1398            }
1399        }
1400        return removeDone;
1401    }
1402
1403    /**
1404     * Performs a timed wait on the started latch and then checks for disposed
1405     * before performing another wait each time the the started wait times out.
1406     */
1407    protected boolean safeWaitUntilStarted() throws InterruptedException {
1408        while (!disposed.get()) {
1409            if (startedLatch.await(1, TimeUnit.SECONDS)) {
1410                break;
1411            }
1412        }
1413        return !disposed.get();
1414    }
1415
1416    protected NetworkBridgeFilter createNetworkBridgeFilter(ConsumerInfo info) throws IOException {
1417        NetworkBridgeFilterFactory filterFactory = defaultFilterFactory;
1418        if (brokerService != null && brokerService.getDestinationPolicy() != null) {
1419            PolicyEntry entry = brokerService.getDestinationPolicy().getEntryFor(info.getDestination());
1420            if (entry != null && entry.getNetworkBridgeFilterFactory() != null) {
1421                filterFactory = entry.getNetworkBridgeFilterFactory();
1422            }
1423        }
1424        return filterFactory.create(info, getRemoteBrokerPath(), configuration.getMessageTTL(), configuration.getConsumerTTL());
1425    }
1426
1427    protected void addRemoteBrokerToBrokerPath(ConsumerInfo info) throws IOException {
1428        info.setBrokerPath(appendToBrokerPath(info.getBrokerPath(), getRemoteBrokerPath()));
1429    }
1430
1431    protected BrokerId[] getRemoteBrokerPath() {
1432        return remoteBrokerPath;
1433    }
1434
1435    @Override
1436    public void setNetworkBridgeListener(NetworkBridgeListener listener) {
1437        this.networkBridgeListener = listener;
1438    }
1439
1440    private void fireBridgeFailed(Throwable reason) {
1441        LOG.trace("fire bridge failed, listener: {}", this.networkBridgeListener, reason);
1442        NetworkBridgeListener l = this.networkBridgeListener;
1443        if (l != null && this.bridgeFailed.compareAndSet(false, true)) {
1444            l.bridgeFailed();
1445        }
1446    }
1447
1448    /**
1449     * @return Returns the dynamicallyIncludedDestinations.
1450     */
1451    public ActiveMQDestination[] getDynamicallyIncludedDestinations() {
1452        return dynamicallyIncludedDestinations;
1453    }
1454
1455    /**
1456     * @param dynamicallyIncludedDestinations
1457     *         The dynamicallyIncludedDestinations to set.
1458     */
1459    public void setDynamicallyIncludedDestinations(ActiveMQDestination[] dynamicallyIncludedDestinations) {
1460        this.dynamicallyIncludedDestinations = dynamicallyIncludedDestinations;
1461    }
1462
1463    /**
1464     * @return Returns the excludedDestinations.
1465     */
1466    public ActiveMQDestination[] getExcludedDestinations() {
1467        return excludedDestinations;
1468    }
1469
1470    /**
1471     * @param excludedDestinations The excludedDestinations to set.
1472     */
1473    public void setExcludedDestinations(ActiveMQDestination[] excludedDestinations) {
1474        this.excludedDestinations = excludedDestinations;
1475    }
1476
1477    /**
1478     * @return Returns the staticallyIncludedDestinations.
1479     */
1480    public ActiveMQDestination[] getStaticallyIncludedDestinations() {
1481        return staticallyIncludedDestinations;
1482    }
1483
1484    /**
1485     * @param staticallyIncludedDestinations The staticallyIncludedDestinations to set.
1486     */
1487    public void setStaticallyIncludedDestinations(ActiveMQDestination[] staticallyIncludedDestinations) {
1488        this.staticallyIncludedDestinations = staticallyIncludedDestinations;
1489    }
1490
1491    /**
1492     * @return Returns the durableDestinations.
1493     */
1494    public ActiveMQDestination[] getDurableDestinations() {
1495        return durableDestinations;
1496    }
1497
1498    /**
1499     * @param durableDestinations The durableDestinations to set.
1500     */
1501    public void setDurableDestinations(ActiveMQDestination[] durableDestinations) {
1502        this.durableDestinations = durableDestinations;
1503    }
1504
1505    /**
1506     * @return Returns the localBroker.
1507     */
1508    public Transport getLocalBroker() {
1509        return localBroker;
1510    }
1511
1512    /**
1513     * @return Returns the remoteBroker.
1514     */
1515    public Transport getRemoteBroker() {
1516        return remoteBroker;
1517    }
1518
1519    /**
1520     * @return the createdByDuplex
1521     */
1522    public boolean isCreatedByDuplex() {
1523        return this.createdByDuplex;
1524    }
1525
1526    /**
1527     * @param createdByDuplex the createdByDuplex to set
1528     */
1529    public void setCreatedByDuplex(boolean createdByDuplex) {
1530        this.createdByDuplex = createdByDuplex;
1531    }
1532
1533    @Override
1534    public String getRemoteAddress() {
1535        return remoteBroker.getRemoteAddress();
1536    }
1537
1538    @Override
1539    public String getLocalAddress() {
1540        return localBroker.getRemoteAddress();
1541    }
1542
1543    @Override
1544    public String getRemoteBrokerName() {
1545        return remoteBrokerInfo == null ? null : remoteBrokerInfo.getBrokerName();
1546    }
1547
1548    @Override
1549    public String getRemoteBrokerId() {
1550        return (remoteBrokerInfo == null || remoteBrokerInfo.getBrokerId() == null) ? null : remoteBrokerInfo.getBrokerId().toString();
1551    }
1552
1553    @Override
1554    public String getLocalBrokerName() {
1555        return localBrokerInfo == null ? null : localBrokerInfo.getBrokerName();
1556    }
1557
1558    @Override
1559    public long getDequeueCounter() {
1560        return dequeueCounter.get();
1561    }
1562
1563    @Override
1564    public long getEnqueueCounter() {
1565        return enqueueCounter.get();
1566    }
1567
1568    protected boolean isDuplex() {
1569        return configuration.isDuplex() || createdByDuplex;
1570    }
1571
1572    public ConcurrentMap<ConsumerId, DemandSubscription> getLocalSubscriptionMap() {
1573        return subscriptionMapByRemoteId;
1574    }
1575
1576    @Override
1577    public void setBrokerService(BrokerService brokerService) {
1578        this.brokerService = brokerService;
1579        this.localBrokerId = brokerService.getRegionBroker().getBrokerId();
1580        localBrokerPath[0] = localBrokerId;
1581    }
1582
1583    @Override
1584    public void setMbeanObjectName(ObjectName objectName) {
1585        this.mbeanObjectName = objectName;
1586    }
1587
1588    @Override
1589    public ObjectName getMbeanObjectName() {
1590        return mbeanObjectName;
1591    }
1592
1593    @Override
1594    public void resetStats() {
1595        enqueueCounter.set(0);
1596        dequeueCounter.set(0);
1597    }
1598
1599    /*
1600     * Used to allow for async tasks to await receipt of the BrokerInfo from the local and
1601     * remote sides of the network bridge.
1602     */
1603    private static class FutureBrokerInfo implements Future<BrokerInfo> {
1604
1605        private final CountDownLatch slot = new CountDownLatch(1);
1606        private final AtomicBoolean disposed;
1607        private volatile BrokerInfo info = null;
1608
1609        public FutureBrokerInfo(BrokerInfo info, AtomicBoolean disposed) {
1610            this.info = info;
1611            this.disposed = disposed;
1612        }
1613
1614        @Override
1615        public boolean cancel(boolean mayInterruptIfRunning) {
1616            slot.countDown();
1617            return true;
1618        }
1619
1620        @Override
1621        public boolean isCancelled() {
1622            return slot.getCount() == 0 && info == null;
1623        }
1624
1625        @Override
1626        public boolean isDone() {
1627            return info != null;
1628        }
1629
1630        @Override
1631        public BrokerInfo get() throws InterruptedException, ExecutionException {
1632            try {
1633                if (info == null) {
1634                    while (!disposed.get()) {
1635                        if (slot.await(1, TimeUnit.SECONDS)) {
1636                            break;
1637                        }
1638                    }
1639                }
1640                return info;
1641            } catch (InterruptedException e) {
1642                Thread.currentThread().interrupt();
1643                LOG.debug("Operation interrupted: {}", e, e);
1644                throw new InterruptedException("Interrupted.");
1645            }
1646        }
1647
1648        @Override
1649        public BrokerInfo get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
1650            try {
1651                if (info == null) {
1652                    long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
1653
1654                    while (!disposed.get() || System.currentTimeMillis() < deadline) {
1655                        if (slot.await(1, TimeUnit.MILLISECONDS)) {
1656                            break;
1657                        }
1658                    }
1659                    if (info == null) {
1660                        throw new TimeoutException();
1661                    }
1662                }
1663                return info;
1664            } catch (InterruptedException e) {
1665                throw new InterruptedException("Interrupted.");
1666            }
1667        }
1668
1669        public void set(BrokerInfo info) {
1670            this.info = info;
1671            this.slot.countDown();
1672        }
1673    }
1674
1675    protected void serviceOutbound(Message message) {
1676        NetworkBridgeListener l = this.networkBridgeListener;
1677        if (l != null) {
1678            l.onOutboundMessage(this, message);
1679        }
1680    }
1681
1682    protected void serviceInboundMessage(Message message) {
1683        NetworkBridgeListener l = this.networkBridgeListener;
1684        if (l != null) {
1685            l.onInboundMessage(this, message);
1686        }
1687    }
1688
1689    protected boolean canDuplexDispatch(Message message) {
1690        boolean result = true;
1691        if (configuration.isCheckDuplicateMessagesOnDuplex()){
1692            final long producerSequenceId = message.getMessageId().getProducerSequenceId();
1693            //  messages are multiplexed on this producer so we need to query the persistenceAdapter
1694            long lastStoredForMessageProducer = getStoredSequenceIdForMessage(message.getMessageId());
1695            if (producerSequenceId <= lastStoredForMessageProducer) {
1696                result = false;
1697                LOG.debug("suppressing duplicate message send [{}] from network producer with producerSequence [{}] less than last stored: {}", new Object[]{
1698                        (LOG.isTraceEnabled() ? message : message.getMessageId()), producerSequenceId, lastStoredForMessageProducer
1699                });
1700            }
1701        }
1702        return result;
1703    }
1704
1705    protected long getStoredSequenceIdForMessage(MessageId messageId) {
1706        try {
1707            return brokerService.getPersistenceAdapter().getLastProducerSequenceId(messageId.getProducerId());
1708        } catch (IOException ignored) {
1709            LOG.debug("Failed to determine last producer sequence id for: {}", messageId, ignored);
1710        }
1711        return -1;
1712    }
1713
1714}