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