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