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