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