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