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