/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.core.client.impl; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Array; import java.net.URI; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException; import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException; import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration; import org.apache.activemq.artemis.api.core.Interceptor; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.TransportConfiguration; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ClusterTopologyListener; import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.api.core.client.TopologyMember; import org.apache.activemq.artemis.api.core.client.loadbalance.ConnectionLoadBalancingPolicy; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle; import org.apache.activemq.artemis.core.cluster.DiscoveryEntry; import org.apache.activemq.artemis.core.cluster.DiscoveryGroup; import org.apache.activemq.artemis.core.cluster.DiscoveryListener; import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQClientProtocolManagerFactory; import org.apache.activemq.artemis.core.remoting.FailureListener; import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager; import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManagerFactory; import org.apache.activemq.artemis.spi.core.remoting.Connector; import org.apache.activemq.artemis.uri.ServerLocatorParser; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.ActiveMQThreadPoolExecutor; import org.apache.activemq.artemis.utils.ClassloadingUtil; import org.apache.activemq.artemis.utils.UUIDGenerator; import org.apache.activemq.artemis.utils.uri.FluentPropertyBeanIntrospectorWithIgnores; import org.jboss.logging.Logger; /** * This is the implementation of {@link org.apache.activemq.artemis.api.core.client.ServerLocator} and all * the proper javadoc is located on that interface. */ public final class ServerLocatorImpl implements ServerLocatorInternal, DiscoveryListener { private static final Logger logger = Logger.getLogger(ServerLocatorImpl.class); private enum STATE { INITIALIZED, CLOSED, CLOSING } static { // this is not really a property, needs to be ignored FluentPropertyBeanIntrospectorWithIgnores.addIgnore(ServerLocatorImpl.class.getName(), "setThreadPools"); } private static final long serialVersionUID = -1615857864410205260L; // This is the default value private ClientProtocolManagerFactory protocolManagerFactory = ActiveMQClientProtocolManagerFactory.getInstance(this); private final boolean ha; private boolean finalizeCheck = true; private boolean clusterConnection; private transient String identity; private final Set<ClientSessionFactoryInternal> factories = new HashSet<>(); private final Set<ClientSessionFactoryInternal> connectingFactories = new HashSet<>(); private volatile TransportConfiguration[] initialConnectors; private final DiscoveryGroupConfiguration discoveryGroupConfiguration; private final StaticConnector staticConnector = new StaticConnector(); private final Topology topology; private final Object topologyArrayGuard = new Object(); private volatile Pair<TransportConfiguration, TransportConfiguration>[] topologyArray; private volatile boolean receivedTopology; private boolean compressLargeMessage; // if the system should shutdown the pool when shutting down private transient boolean shutdownPool; private transient ExecutorService threadPool; private transient ScheduledExecutorService scheduledThreadPool; private transient DiscoveryGroup discoveryGroup; private transient ConnectionLoadBalancingPolicy loadBalancingPolicy; // Settable attributes: private boolean cacheLargeMessagesClient; private long clientFailureCheckPeriod; private long connectionTTL; private long callTimeout; private long callFailoverTimeout; private int minLargeMessageSize; private int consumerWindowSize; private int consumerMaxRate; private int confirmationWindowSize; private int producerWindowSize; private int producerMaxRate; private boolean blockOnAcknowledge; private boolean blockOnDurableSend; private boolean blockOnNonDurableSend; private boolean autoGroup; private boolean preAcknowledge; private String connectionLoadBalancingPolicyClassName; private int ackBatchSize; private boolean useGlobalPools; private int scheduledThreadPoolMaxSize; private int threadPoolMaxSize; private long retryInterval; private double retryIntervalMultiplier; private long maxRetryInterval; private int reconnectAttempts; private int initialConnectAttempts; private boolean failoverOnInitialConnection; private int initialMessagePacketSize; private final Object stateGuard = new Object(); private transient STATE state; private transient CountDownLatch latch; private final List<Interceptor> incomingInterceptors = new CopyOnWriteArrayList<>(); private final List<Interceptor> outgoingInterceptors = new CopyOnWriteArrayList<>(); private Executor startExecutor; private AfterConnectInternalListener afterConnectListener; private String groupID; private String nodeID; private TransportConfiguration clusterTransportConfiguration; private final Exception traceException = new Exception(); // To be called when there are ServerLocator being finalized. // To be used on test assertions public static Runnable finalizeCallback = null; public static synchronized void clearThreadPools() { ActiveMQClient.clearThreadPools(); } private synchronized void setThreadPools() { if (threadPool != null) { return; } else if (useGlobalPools) { threadPool = ActiveMQClient.getGlobalThreadPool(); scheduledThreadPool = ActiveMQClient.getGlobalScheduledThreadPool(); } else { this.shutdownPool = true; ThreadFactory factory = AccessController.doPrivileged(new PrivilegedAction<ThreadFactory>() { @Override public ThreadFactory run() { return new ActiveMQThreadFactory("ActiveMQ-client-factory-threads-" + System.identityHashCode(this), true, ClientSessionFactoryImpl.class.getClassLoader()); } }); if (threadPoolMaxSize == -1) { threadPool = Executors.newCachedThreadPool(factory); } else { threadPool = new ActiveMQThreadPoolExecutor(0, threadPoolMaxSize, 60L, TimeUnit.SECONDS, factory); } factory = AccessController.doPrivileged(new PrivilegedAction<ThreadFactory>() { @Override public ThreadFactory run() { return new ActiveMQThreadFactory("ActiveMQ-client-factory-pinger-threads-" + System.identityHashCode(this), true, ClientSessionFactoryImpl.class.getClassLoader()); } }); scheduledThreadPool = Executors.newScheduledThreadPool(scheduledThreadPoolMaxSize, factory); } } @Override public synchronized boolean setThreadPools(ExecutorService threadPool, ScheduledExecutorService scheduledThreadPool) { if (threadPool == null || scheduledThreadPool == null) return false; if (this.threadPool == null && this.scheduledThreadPool == null) { useGlobalPools = false; shutdownPool = false; this.threadPool = threadPool; this.scheduledThreadPool = scheduledThreadPool; return true; } else { return false; } } private void instantiateLoadBalancingPolicy() { if (connectionLoadBalancingPolicyClassName == null) { throw new IllegalStateException("Please specify a load balancing policy class name on the session factory"); } AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { loadBalancingPolicy = (ConnectionLoadBalancingPolicy) ClassloadingUtil.newInstanceFromClassLoader(connectionLoadBalancingPolicyClassName); return null; } }); } private synchronized void initialise() throws ActiveMQException { if (state == STATE.INITIALIZED) return; synchronized (stateGuard) { if (state == STATE.CLOSING) throw new ActiveMQIllegalStateException(); try { state = STATE.INITIALIZED; latch = new CountDownLatch(1); setThreadPools(); instantiateLoadBalancingPolicy(); if (discoveryGroupConfiguration != null) { discoveryGroup = createDiscoveryGroup(nodeID, discoveryGroupConfiguration); discoveryGroup.registerListener(this); discoveryGroup.start(); } } catch (Exception e) { state = null; throw ActiveMQClientMessageBundle.BUNDLE.failedToInitialiseSessionFactory(e); } } } private static DiscoveryGroup createDiscoveryGroup(String nodeID, DiscoveryGroupConfiguration config) throws Exception { DiscoveryGroup group = new DiscoveryGroup(nodeID, config.getName(), config.getRefreshTimeout(), config.getBroadcastEndpointFactory(), null); return group; } private ServerLocatorImpl(final Topology topology, final boolean useHA, final DiscoveryGroupConfiguration discoveryGroupConfiguration, final TransportConfiguration[] transportConfigs) { traceException.fillInStackTrace(); this.topology = topology == null ? new Topology(this) : topology; this.ha = useHA; this.discoveryGroupConfiguration = discoveryGroupConfiguration; this.initialConnectors = transportConfigs != null ? transportConfigs : null; this.nodeID = UUIDGenerator.getInstance().generateStringUUID(); clientFailureCheckPeriod = ActiveMQClient.DEFAULT_CLIENT_FAILURE_CHECK_PERIOD; connectionTTL = ActiveMQClient.DEFAULT_CONNECTION_TTL; callTimeout = ActiveMQClient.DEFAULT_CALL_TIMEOUT; callFailoverTimeout = ActiveMQClient.DEFAULT_CALL_FAILOVER_TIMEOUT; minLargeMessageSize = ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE; consumerWindowSize = ActiveMQClient.DEFAULT_CONSUMER_WINDOW_SIZE; consumerMaxRate = ActiveMQClient.DEFAULT_CONSUMER_MAX_RATE; confirmationWindowSize = ActiveMQClient.DEFAULT_CONFIRMATION_WINDOW_SIZE; producerWindowSize = ActiveMQClient.DEFAULT_PRODUCER_WINDOW_SIZE; producerMaxRate = ActiveMQClient.DEFAULT_PRODUCER_MAX_RATE; blockOnAcknowledge = ActiveMQClient.DEFAULT_BLOCK_ON_ACKNOWLEDGE; blockOnDurableSend = ActiveMQClient.DEFAULT_BLOCK_ON_DURABLE_SEND; blockOnNonDurableSend = ActiveMQClient.DEFAULT_BLOCK_ON_NON_DURABLE_SEND; autoGroup = ActiveMQClient.DEFAULT_AUTO_GROUP; preAcknowledge = ActiveMQClient.DEFAULT_PRE_ACKNOWLEDGE; ackBatchSize = ActiveMQClient.DEFAULT_ACK_BATCH_SIZE; connectionLoadBalancingPolicyClassName = ActiveMQClient.DEFAULT_CONNECTION_LOAD_BALANCING_POLICY_CLASS_NAME; useGlobalPools = ActiveMQClient.DEFAULT_USE_GLOBAL_POOLS; threadPoolMaxSize = ActiveMQClient.DEFAULT_THREAD_POOL_MAX_SIZE; scheduledThreadPoolMaxSize = ActiveMQClient.DEFAULT_SCHEDULED_THREAD_POOL_MAX_SIZE; retryInterval = ActiveMQClient.DEFAULT_RETRY_INTERVAL; retryIntervalMultiplier = ActiveMQClient.DEFAULT_RETRY_INTERVAL_MULTIPLIER; maxRetryInterval = ActiveMQClient.DEFAULT_MAX_RETRY_INTERVAL; reconnectAttempts = ActiveMQClient.DEFAULT_RECONNECT_ATTEMPTS; initialConnectAttempts = ActiveMQClient.INITIAL_CONNECT_ATTEMPTS; failoverOnInitialConnection = ActiveMQClient.DEFAULT_FAILOVER_ON_INITIAL_CONNECTION; cacheLargeMessagesClient = ActiveMQClient.DEFAULT_CACHE_LARGE_MESSAGE_CLIENT; initialMessagePacketSize = ActiveMQClient.DEFAULT_INITIAL_MESSAGE_PACKET_SIZE; cacheLargeMessagesClient = ActiveMQClient.DEFAULT_CACHE_LARGE_MESSAGE_CLIENT; compressLargeMessage = ActiveMQClient.DEFAULT_COMPRESS_LARGE_MESSAGES; clusterConnection = false; } public static ServerLocator newLocator(String uri) { try { ServerLocatorParser parser = new ServerLocatorParser(); URI newURI = parser.expandURI(uri); return parser.newObject(newURI, null); } catch (Exception e) { throw new RuntimeException(e); } } public static ServerLocator newLocator(URI uri) { try { ServerLocatorParser parser = new ServerLocatorParser(); return parser.newObject(uri, null); } catch (Exception e) { throw new RuntimeException(e); } } /** * Create a ServerLocatorImpl using UDP discovery to lookup cluster */ public ServerLocatorImpl(final boolean useHA, final DiscoveryGroupConfiguration groupConfiguration) { this(new Topology(null), useHA, groupConfiguration, null); if (useHA) { // We only set the owner at where the Topology was created. // For that reason we can't set it at the main constructor topology.setOwner(this); } } /** * Create a ServerLocatorImpl using a static list of live servers * * @param transportConfigs */ public ServerLocatorImpl(final boolean useHA, final TransportConfiguration... transportConfigs) { this(new Topology(null), useHA, null, transportConfigs); if (useHA) { // We only set the owner at where the Topology was created. // For that reason we can't set it at the main constructor topology.setOwner(this); } } /** * Create a ServerLocatorImpl using UDP discovery to lookup cluster */ public ServerLocatorImpl(final Topology topology, final boolean useHA, final DiscoveryGroupConfiguration groupConfiguration) { this(topology, useHA, groupConfiguration, null); } /** * Create a ServerLocatorImpl using a static list of live servers * * @param transportConfigs */ public ServerLocatorImpl(final Topology topology, final boolean useHA, final TransportConfiguration... transportConfigs) { this(topology, useHA, null, transportConfigs); } @Override public void resetToInitialConnectors() { receivedTopology = false; topologyArray = null; topology.clear(); } /* * I'm not using isAllInVM here otherwsie BeanProperties would translate this as a property for the URL */ @Override public boolean allInVM() { for (TransportConfiguration config : getStaticTransportConfigurations()) { if (!config.getFactoryClassName().contains("InVMConnectorFactory")) { return false; } } return true; } private ServerLocatorImpl(ServerLocatorImpl locator) { ha = locator.ha; finalizeCheck = locator.finalizeCheck; clusterConnection = locator.clusterConnection; initialConnectors = locator.initialConnectors; discoveryGroupConfiguration = locator.discoveryGroupConfiguration; topology = locator.topology; topologyArray = locator.topologyArray; receivedTopology = locator.receivedTopology; compressLargeMessage = locator.compressLargeMessage; cacheLargeMessagesClient = locator.cacheLargeMessagesClient; clientFailureCheckPeriod = locator.clientFailureCheckPeriod; connectionTTL = locator.connectionTTL; callTimeout = locator.callTimeout; callFailoverTimeout = locator.callFailoverTimeout; minLargeMessageSize = locator.minLargeMessageSize; consumerWindowSize = locator.consumerWindowSize; consumerMaxRate = locator.consumerMaxRate; confirmationWindowSize = locator.confirmationWindowSize; producerWindowSize = locator.producerWindowSize; producerMaxRate = locator.producerMaxRate; blockOnAcknowledge = locator.blockOnAcknowledge; blockOnDurableSend = locator.blockOnDurableSend; blockOnNonDurableSend = locator.blockOnNonDurableSend; autoGroup = locator.autoGroup; preAcknowledge = locator.preAcknowledge; connectionLoadBalancingPolicyClassName = locator.connectionLoadBalancingPolicyClassName; ackBatchSize = locator.ackBatchSize; useGlobalPools = locator.useGlobalPools; scheduledThreadPoolMaxSize = locator.scheduledThreadPoolMaxSize; threadPoolMaxSize = locator.threadPoolMaxSize; retryInterval = locator.retryInterval; retryIntervalMultiplier = locator.retryIntervalMultiplier; maxRetryInterval = locator.maxRetryInterval; reconnectAttempts = locator.reconnectAttempts; initialConnectAttempts = locator.initialConnectAttempts; failoverOnInitialConnection = locator.failoverOnInitialConnection; initialMessagePacketSize = locator.initialMessagePacketSize; startExecutor = locator.startExecutor; afterConnectListener = locator.afterConnectListener; groupID = locator.groupID; nodeID = locator.nodeID; clusterTransportConfiguration = locator.clusterTransportConfiguration; } private TransportConfiguration selectConnector() { Pair<TransportConfiguration, TransportConfiguration>[] usedTopology; synchronized (topologyArrayGuard) { usedTopology = topologyArray; } synchronized (this) { // if the topologyArray is null, we will use the initialConnectors if (usedTopology != null) { if (logger.isTraceEnabled()) { logger.trace("Selecting connector from topology."); } int pos = loadBalancingPolicy.select(usedTopology.length); Pair<TransportConfiguration, TransportConfiguration> pair = usedTopology[pos]; return pair.getA(); } else { // Get from initialconnectors if (logger.isTraceEnabled()) { logger.trace("Selecting connector from initial connectors."); } int pos = loadBalancingPolicy.select(initialConnectors.length); return initialConnectors[pos]; } } } @Override public void start(Executor executor) throws Exception { initialise(); this.startExecutor = executor; if (executor != null) { executor.execute(new Runnable() { @Override public void run() { try { connect(); } catch (Exception e) { if (!isClosed()) { ActiveMQClientLogger.LOGGER.errorConnectingToNodes(e); } } } }); } } @Override public ClientProtocolManager newProtocolManager() { return getProtocolManagerFactory().newProtocolManager(); } @Override public ClientProtocolManagerFactory getProtocolManagerFactory() { if (protocolManagerFactory == null) { // Default one in case it's null protocolManagerFactory = ActiveMQClientProtocolManagerFactory.getInstance(this); } return protocolManagerFactory; } @Override public ServerLocator setProtocolManagerFactory(ClientProtocolManagerFactory protocolManagerFactory) { this.protocolManagerFactory = protocolManagerFactory; protocolManagerFactory.setLocator(this); return this; } @Override public void disableFinalizeCheck() { finalizeCheck = false; } @Override public ClientSessionFactoryInternal connect() throws ActiveMQException { return connect(false); } private ClientSessionFactoryInternal connect(final boolean skipWarnings) throws ActiveMQException { ClientSessionFactoryInternal returnFactory = null; synchronized (this) { // static list of initial connectors if (getNumInitialConnectors() > 0 && discoveryGroup == null) { returnFactory = (ClientSessionFactoryInternal) staticConnector.connect(skipWarnings); } } if (returnFactory != null) { addFactory(returnFactory); return returnFactory; } else { // wait for discovery group to get the list of initial connectors return (ClientSessionFactoryInternal) createSessionFactory(); } } @Override public ClientSessionFactoryInternal connectNoWarnings() throws ActiveMQException { return connect(true); } @Override public ServerLocatorImpl setAfterConnectionInternalListener(AfterConnectInternalListener listener) { this.afterConnectListener = listener; return this; } @Override public AfterConnectInternalListener getAfterConnectInternalListener() { return afterConnectListener; } @Override public ClientSessionFactory createSessionFactory(String nodeID) throws Exception { TopologyMember topologyMember = topology.getMember(nodeID); if (logger.isTraceEnabled()) { logger.trace("Creating connection factory towards " + nodeID + " = " + topologyMember + ", topology=" + topology.describe()); } if (topologyMember == null) { return null; } if (topologyMember.getLive() != null) { ClientSessionFactoryInternal factory = (ClientSessionFactoryInternal) createSessionFactory(topologyMember.getLive()); if (topologyMember.getBackup() != null) { factory.setBackupConnector(topologyMember.getLive(), topologyMember.getBackup()); } return factory; } if (topologyMember.getLive() == null && topologyMember.getBackup() != null) { // This shouldn't happen, however I wanted this to consider all possible cases ClientSessionFactoryInternal factory = (ClientSessionFactoryInternal) createSessionFactory(topologyMember.getBackup()); return factory; } // it shouldn't happen return null; } @Override public ClientSessionFactory createSessionFactory(final TransportConfiguration transportConfiguration) throws Exception { assertOpen(); initialise(); ClientSessionFactoryInternal factory = new ClientSessionFactoryImpl(this, transportConfiguration, callTimeout, callFailoverTimeout, clientFailureCheckPeriod, connectionTTL, retryInterval, retryIntervalMultiplier, maxRetryInterval, reconnectAttempts, threadPool, scheduledThreadPool, incomingInterceptors, outgoingInterceptors); addToConnecting(factory); try { try { factory.connect(reconnectAttempts, failoverOnInitialConnection); } catch (ActiveMQException e1) { //we need to make sure is closed just for garbage collection factory.close(); throw e1; } addFactory(factory); return factory; } finally { removeFromConnecting(factory); } } @Override public ClientSessionFactory createSessionFactory(final TransportConfiguration transportConfiguration, int reconnectAttempts, boolean failoverOnInitialConnection) throws Exception { assertOpen(); initialise(); ClientSessionFactoryInternal factory = new ClientSessionFactoryImpl(this, transportConfiguration, callTimeout, callFailoverTimeout, clientFailureCheckPeriod, connectionTTL, retryInterval, retryIntervalMultiplier, maxRetryInterval, reconnectAttempts, threadPool, scheduledThreadPool, incomingInterceptors, outgoingInterceptors); addToConnecting(factory); try { try { factory.connect(reconnectAttempts, failoverOnInitialConnection); } catch (ActiveMQException e1) { //we need to make sure is closed just for garbage collection factory.close(); throw e1; } addFactory(factory); return factory; } finally { removeFromConnecting(factory); } } private void removeFromConnecting(ClientSessionFactoryInternal factory) { synchronized (connectingFactories) { connectingFactories.remove(factory); } } private void addToConnecting(ClientSessionFactoryInternal factory) { synchronized (connectingFactories) { assertOpen(); connectingFactories.add(factory); } } @Override public ClientSessionFactory createSessionFactory() throws ActiveMQException { assertOpen(); initialise(); if (this.getNumInitialConnectors() == 0 && discoveryGroup != null) { // Wait for an initial broadcast to give us at least one node in the cluster long timeout = clusterConnection ? 0 : discoveryGroupConfiguration.getDiscoveryInitialWaitTimeout(); boolean ok = discoveryGroup.waitForBroadcast(timeout); if (!ok) { throw ActiveMQClientMessageBundle.BUNDLE.connectionTimedOutInInitialBroadcast(); } } ClientSessionFactoryInternal factory = null; synchronized (this) { boolean retry; int attempts = 0; do { retry = false; TransportConfiguration tc = selectConnector(); if (tc == null) { throw ActiveMQClientMessageBundle.BUNDLE.noTCForSessionFactory(); } // try each factory in the list until we find one which works try { factory = new ClientSessionFactoryImpl(this, tc, callTimeout, callFailoverTimeout, clientFailureCheckPeriod, connectionTTL, retryInterval, retryIntervalMultiplier, maxRetryInterval, reconnectAttempts, threadPool, scheduledThreadPool, incomingInterceptors, outgoingInterceptors); try { addToConnecting(factory); factory.connect(initialConnectAttempts, failoverOnInitialConnection); } finally { removeFromConnecting(factory); } } catch (ActiveMQException e) { factory.close(); if (e.getType() == ActiveMQExceptionType.NOT_CONNECTED) { attempts++; synchronized (topologyArrayGuard) { if (topologyArray != null && attempts == topologyArray.length) { throw ActiveMQClientMessageBundle.BUNDLE.cannotConnectToServers(); } if (topologyArray == null && attempts == this.getNumInitialConnectors()) { throw ActiveMQClientMessageBundle.BUNDLE.cannotConnectToServers(); } } retry = true; } else { throw e; } } } while (retry); } // ATM topology is never != null. Checking here just to be consistent with // how the sendSubscription happens. // in case this ever changes. if (topology != null && !factory.waitForTopology(callTimeout, TimeUnit.MILLISECONDS)) { factory.cleanup(); throw ActiveMQClientMessageBundle.BUNDLE.connectionTimedOutOnReceiveTopology(discoveryGroup); } addFactory(factory); return factory; } @Override public boolean isHA() { return ha; } /** * @param interceptorList a comma separated string of incoming interceptor class names to be used. Each interceptor needs a default Constructor to be used with this method. * @return this */ @Override public ServerLocator setIncomingInterceptorList(String interceptorList) { feedInterceptors(incomingInterceptors, interceptorList); return this; } @Override public String getIncomingInterceptorList() { return fromInterceptors(incomingInterceptors); } /** * @param interceptorList a comma separated string of incoming interceptor class names to be used. Each interceptor needs a default Constructor to be used with this method. * @return this */ @Override public ServerLocator setOutgoingInterceptorList(String interceptorList) { feedInterceptors(outgoingInterceptors, interceptorList); return this; } @Override public String getOutgoingInterceptorList() { return fromInterceptors(outgoingInterceptors); } @Override public boolean isCacheLargeMessagesClient() { return cacheLargeMessagesClient; } @Override public ServerLocatorImpl setCacheLargeMessagesClient(final boolean cached) { cacheLargeMessagesClient = cached; return this; } @Override public long getClientFailureCheckPeriod() { return clientFailureCheckPeriod; } @Override public ServerLocatorImpl setClientFailureCheckPeriod(final long clientFailureCheckPeriod) { checkWrite(); this.clientFailureCheckPeriod = clientFailureCheckPeriod; return this; } @Override public long getConnectionTTL() { return connectionTTL; } @Override public ServerLocatorImpl setConnectionTTL(final long connectionTTL) { checkWrite(); this.connectionTTL = connectionTTL; return this; } @Override public long getCallTimeout() { return callTimeout; } @Override public ServerLocatorImpl setCallTimeout(final long callTimeout) { checkWrite(); this.callTimeout = callTimeout; return this; } @Override public long getCallFailoverTimeout() { return callFailoverTimeout; } @Override public ServerLocatorImpl setCallFailoverTimeout(long callFailoverTimeout) { checkWrite(); this.callFailoverTimeout = callFailoverTimeout; return this; } @Override public int getMinLargeMessageSize() { return minLargeMessageSize; } @Override public ServerLocatorImpl setMinLargeMessageSize(final int minLargeMessageSize) { checkWrite(); this.minLargeMessageSize = minLargeMessageSize; return this; } @Override public int getConsumerWindowSize() { return consumerWindowSize; } @Override public ServerLocatorImpl setConsumerWindowSize(final int consumerWindowSize) { checkWrite(); this.consumerWindowSize = consumerWindowSize; return this; } @Override public int getConsumerMaxRate() { return consumerMaxRate; } @Override public ServerLocatorImpl setConsumerMaxRate(final int consumerMaxRate) { checkWrite(); this.consumerMaxRate = consumerMaxRate; return this; } @Override public int getConfirmationWindowSize() { return confirmationWindowSize; } @Override public ServerLocatorImpl setConfirmationWindowSize(final int confirmationWindowSize) { checkWrite(); this.confirmationWindowSize = confirmationWindowSize; return this; } @Override public int getProducerWindowSize() { return producerWindowSize; } @Override public ServerLocatorImpl setProducerWindowSize(final int producerWindowSize) { checkWrite(); this.producerWindowSize = producerWindowSize; return this; } @Override public int getProducerMaxRate() { return producerMaxRate; } @Override public ServerLocatorImpl setProducerMaxRate(final int producerMaxRate) { checkWrite(); this.producerMaxRate = producerMaxRate; return this; } @Override public boolean isBlockOnAcknowledge() { return blockOnAcknowledge; } @Override public ServerLocatorImpl setBlockOnAcknowledge(final boolean blockOnAcknowledge) { checkWrite(); this.blockOnAcknowledge = blockOnAcknowledge; return this; } @Override public boolean isBlockOnDurableSend() { return blockOnDurableSend; } @Override public ServerLocatorImpl setBlockOnDurableSend(final boolean blockOnDurableSend) { checkWrite(); this.blockOnDurableSend = blockOnDurableSend; return this; } @Override public boolean isBlockOnNonDurableSend() { return blockOnNonDurableSend; } @Override public ServerLocatorImpl setBlockOnNonDurableSend(final boolean blockOnNonDurableSend) { checkWrite(); this.blockOnNonDurableSend = blockOnNonDurableSend; return this; } @Override public boolean isAutoGroup() { return autoGroup; } @Override public ServerLocatorImpl setAutoGroup(final boolean autoGroup) { checkWrite(); this.autoGroup = autoGroup; return this; } @Override public boolean isPreAcknowledge() { return preAcknowledge; } @Override public ServerLocatorImpl setPreAcknowledge(final boolean preAcknowledge) { checkWrite(); this.preAcknowledge = preAcknowledge; return this; } @Override public int getAckBatchSize() { return ackBatchSize; } @Override public ServerLocatorImpl setAckBatchSize(final int ackBatchSize) { checkWrite(); this.ackBatchSize = ackBatchSize; return this; } @Override public boolean isUseGlobalPools() { return useGlobalPools; } @Override public ServerLocatorImpl setUseGlobalPools(final boolean useGlobalPools) { checkWrite(); this.useGlobalPools = useGlobalPools; return this; } @Override public int getScheduledThreadPoolMaxSize() { return scheduledThreadPoolMaxSize; } @Override public ServerLocatorImpl setScheduledThreadPoolMaxSize(final int scheduledThreadPoolMaxSize) { checkWrite(); this.scheduledThreadPoolMaxSize = scheduledThreadPoolMaxSize; return this; } @Override public int getThreadPoolMaxSize() { return threadPoolMaxSize; } @Override public ServerLocatorImpl setThreadPoolMaxSize(final int threadPoolMaxSize) { checkWrite(); this.threadPoolMaxSize = threadPoolMaxSize; return this; } @Override public long getRetryInterval() { return retryInterval; } @Override public ServerLocatorImpl setRetryInterval(final long retryInterval) { checkWrite(); this.retryInterval = retryInterval; return this; } @Override public long getMaxRetryInterval() { return maxRetryInterval; } @Override public ServerLocatorImpl setMaxRetryInterval(final long retryInterval) { checkWrite(); maxRetryInterval = retryInterval; return this; } @Override public double getRetryIntervalMultiplier() { return retryIntervalMultiplier; } @Override public ServerLocatorImpl setRetryIntervalMultiplier(final double retryIntervalMultiplier) { checkWrite(); this.retryIntervalMultiplier = retryIntervalMultiplier; return this; } @Override public int getReconnectAttempts() { return reconnectAttempts; } @Override public ServerLocatorImpl setReconnectAttempts(final int reconnectAttempts) { checkWrite(); this.reconnectAttempts = reconnectAttempts; return this; } @Override public ServerLocatorImpl setInitialConnectAttempts(int initialConnectAttempts) { checkWrite(); this.initialConnectAttempts = initialConnectAttempts; return this; } @Override public int getInitialConnectAttempts() { return initialConnectAttempts; } @Override public boolean isFailoverOnInitialConnection() { return this.failoverOnInitialConnection; } @Override public ServerLocatorImpl setFailoverOnInitialConnection(final boolean failover) { checkWrite(); this.failoverOnInitialConnection = failover; return this; } @Override public String getConnectionLoadBalancingPolicyClassName() { return connectionLoadBalancingPolicyClassName; } @Override public ServerLocatorImpl setConnectionLoadBalancingPolicyClassName(final String loadBalancingPolicyClassName) { checkWrite(); connectionLoadBalancingPolicyClassName = loadBalancingPolicyClassName; return this; } @Override public TransportConfiguration[] getStaticTransportConfigurations() { if (initialConnectors == null) return new TransportConfiguration[]{}; return Arrays.copyOf(initialConnectors, initialConnectors.length); } @Override public DiscoveryGroupConfiguration getDiscoveryGroupConfiguration() { return discoveryGroupConfiguration; } @Override public ServerLocatorImpl addIncomingInterceptor(final Interceptor interceptor) { incomingInterceptors.add(interceptor); return this; } @Override public ServerLocatorImpl addOutgoingInterceptor(final Interceptor interceptor) { outgoingInterceptors.add(interceptor); return this; } @Override public boolean removeIncomingInterceptor(final Interceptor interceptor) { return incomingInterceptors.remove(interceptor); } @Override public boolean removeOutgoingInterceptor(final Interceptor interceptor) { return outgoingInterceptors.remove(interceptor); } @Override public int getInitialMessagePacketSize() { return initialMessagePacketSize; } @Override public ServerLocatorImpl setInitialMessagePacketSize(final int size) { checkWrite(); initialMessagePacketSize = size; return this; } @Override public ServerLocatorImpl setGroupID(final String groupID) { checkWrite(); this.groupID = groupID; return this; } @Override public String getGroupID() { return groupID; } @Override public boolean isCompressLargeMessage() { return compressLargeMessage; } @Override public ServerLocatorImpl setCompressLargeMessage(boolean avoid) { this.compressLargeMessage = avoid; return this; } private void checkWrite() { synchronized (stateGuard) { if (state != null && state != STATE.CLOSED) { throw new IllegalStateException("Cannot set attribute on SessionFactory after it has been used"); } } } private int getNumInitialConnectors() { if (initialConnectors == null) return 0; return initialConnectors.length; } @Override public ServerLocatorImpl setIdentity(String identity) { this.identity = identity; return this; } @Override public ServerLocatorImpl setNodeID(String nodeID) { this.nodeID = nodeID; return this; } @Override public String getNodeID() { return nodeID; } @Override public ServerLocatorImpl setClusterConnection(boolean clusterConnection) { this.clusterConnection = clusterConnection; return this; } @Override public boolean isClusterConnection() { return clusterConnection; } @Override public TransportConfiguration getClusterTransportConfiguration() { return clusterTransportConfiguration; } @Override public ServerLocatorImpl setClusterTransportConfiguration(TransportConfiguration tc) { this.clusterTransportConfiguration = tc; return this; } @Override protected void finalize() throws Throwable { if (finalizeCheck) { close(); } super.finalize(); } @Override public void cleanup() { doClose(false); } @Override public void close() { doClose(true); } private void doClose(final boolean sendClose) { synchronized (stateGuard) { if (state == STATE.CLOSED) { if (logger.isDebugEnabled()) { logger.debug(this + " is already closed when calling closed"); } return; } state = STATE.CLOSING; } if (latch != null) latch.countDown(); synchronized (connectingFactories) { for (ClientSessionFactoryInternal csf : connectingFactories) { csf.causeExit(); } } if (discoveryGroup != null) { synchronized (this) { try { discoveryGroup.stop(); } catch (Exception e) { ActiveMQClientLogger.LOGGER.failedToStopDiscovery(e); } } } else { staticConnector.disconnect(); } synchronized (connectingFactories) { for (ClientSessionFactoryInternal csf : connectingFactories) { csf.causeExit(); } for (ClientSessionFactoryInternal csf : connectingFactories) { csf.close(); } connectingFactories.clear(); } Set<ClientSessionFactoryInternal> clonedFactory; synchronized (factories) { clonedFactory = new HashSet<>(factories); factories.clear(); } for (ClientSessionFactoryInternal factory : clonedFactory) { factory.causeExit(); } for (ClientSessionFactory factory : clonedFactory) { if (sendClose) { factory.close(); } else { factory.cleanup(); } } if (shutdownPool) { if (threadPool != null) { threadPool.shutdown(); try { if (!threadPool.awaitTermination(10000, TimeUnit.MILLISECONDS)) { ActiveMQClientLogger.LOGGER.timedOutWaitingForTermination(); } } catch (InterruptedException e) { throw new ActiveMQInterruptedException(e); } } if (scheduledThreadPool != null) { scheduledThreadPool.shutdown(); try { if (!scheduledThreadPool.awaitTermination(10000, TimeUnit.MILLISECONDS)) { ActiveMQClientLogger.LOGGER.timedOutWaitingForScheduledPoolTermination(); } } catch (InterruptedException e) { throw new ActiveMQInterruptedException(e); } } } synchronized (stateGuard) { state = STATE.CLOSED; } } /** * This is directly called when the connection to the node is gone, * or when the node sends a disconnection. * Look for callers of this method! */ @Override public void notifyNodeDown(final long eventTime, final String nodeID) { if (!ha) { // there's no topology here return; } if (logger.isTraceEnabled()) { logger.trace("nodeDown " + this + " nodeID=" + nodeID + " as being down", new Exception("trace")); } topology.removeMember(eventTime, nodeID); if (clusterConnection) { updateArraysAndPairs(); } else { if (topology.isEmpty()) { // Resetting the topology to its original condition as it was brand new receivedTopology = false; topologyArray = null; } else { updateArraysAndPairs(); if (topology.nodes() == 1 && topology.getMember(this.nodeID) != null) { // Resetting the topology to its original condition as it was brand new receivedTopology = false; } } } } @Override public void notifyNodeUp(long uniqueEventID, final String nodeID, final String backupGroupName, final String scaleDownGroupName, final Pair<TransportConfiguration, TransportConfiguration> connectorPair, final boolean last) { if (logger.isTraceEnabled()) { logger.trace("NodeUp " + this + "::nodeID=" + nodeID + ", connectorPair=" + connectorPair, new Exception("trace")); } TopologyMemberImpl member = new TopologyMemberImpl(nodeID, backupGroupName, scaleDownGroupName, connectorPair.getA(), connectorPair.getB()); topology.updateMember(uniqueEventID, nodeID, member); TopologyMember actMember = topology.getMember(nodeID); if (actMember != null && actMember.getLive() != null && actMember.getBackup() != null) { HashSet<ClientSessionFactory> clonedFactories = new HashSet<>(); synchronized (factories) { clonedFactories.addAll(factories); } for (ClientSessionFactory factory : clonedFactories) { ((ClientSessionFactoryInternal) factory).setBackupConnector(actMember.getLive(), actMember.getBackup()); } } updateArraysAndPairs(); if (last) { receivedTopology = true; } } @Override public String toString() { if (identity != null) { return "ServerLocatorImpl (identity=" + identity + ") [initialConnectors=" + Arrays.toString(initialConnectors == null ? new TransportConfiguration[0] : initialConnectors) + ", discoveryGroupConfiguration=" + discoveryGroupConfiguration + "]"; } return "ServerLocatorImpl [initialConnectors=" + Arrays.toString(initialConnectors == null ? new TransportConfiguration[0] : initialConnectors) + ", discoveryGroupConfiguration=" + discoveryGroupConfiguration + "]"; } @SuppressWarnings("unchecked") private void updateArraysAndPairs() { synchronized (topologyArrayGuard) { Collection<TopologyMemberImpl> membersCopy = topology.getMembers(); Pair<TransportConfiguration, TransportConfiguration>[] topologyArrayLocal = (Pair<TransportConfiguration, TransportConfiguration>[]) Array.newInstance(Pair.class, membersCopy.size()); int count = 0; for (TopologyMemberImpl pair : membersCopy) { topologyArrayLocal[count++] = pair.getConnector(); } this.topologyArray = topologyArrayLocal; } } @Override public synchronized void connectorsChanged(List<DiscoveryEntry> newConnectors) { if (receivedTopology) { return; } TransportConfiguration[] newInitialconnectors = (TransportConfiguration[]) Array.newInstance(TransportConfiguration.class, newConnectors.size()); int count = 0; for (DiscoveryEntry entry : newConnectors) { newInitialconnectors[count++] = entry.getConnector(); if (ha && topology.getMember(entry.getNodeID()) == null) { TopologyMemberImpl member = new TopologyMemberImpl(entry.getNodeID(), null, null, entry.getConnector(), null); // on this case we set it as zero as any update coming from server should be accepted topology.updateMember(0, entry.getNodeID(), member); } } this.initialConnectors = newInitialconnectors.length == 0 ? null : newInitialconnectors; if (clusterConnection && !receivedTopology && this.getNumInitialConnectors() > 0) { // The node is alone in the cluster. We create a connection to the new node // to trigger the node notification to form the cluster. Runnable connectRunnable = new Runnable() { @Override public void run() { try { connect(); } catch (ActiveMQException e) { ActiveMQClientLogger.LOGGER.errorConnectingToNodes(e); } } }; if (startExecutor != null) { startExecutor.execute(connectRunnable); } else { connectRunnable.run(); } } } @Override public void factoryClosed(final ClientSessionFactory factory) { boolean isEmpty; synchronized (factories) { factories.remove(factory); isEmpty = factories.isEmpty(); } if (!clusterConnection && isEmpty) { receivedTopology = false; topologyArray = null; } } @Override public Topology getTopology() { return topology; } @Override public boolean isConnectable() { return getNumInitialConnectors() > 0 || getDiscoveryGroupConfiguration() != null; } @Override public ServerLocatorImpl addClusterTopologyListener(final ClusterTopologyListener listener) { topology.addClusterTopologyListener(listener); return this; } @Override public void removeClusterTopologyListener(final ClusterTopologyListener listener) { topology.removeClusterTopologyListener(listener); } /** * for tests only and not part of the public interface. Do not use it. * * @return */ public TransportConfiguration[] getInitialConnectors() { return initialConnectors; } private void addFactory(ClientSessionFactoryInternal factory) { if (factory == null) { return; } if (isClosed()) { factory.close(); return; } TransportConfiguration backup = null; if (ha) { backup = topology.getBackupForConnector((Connector) factory.getConnector()); } factory.setBackupConnector(factory.getConnectorConfiguration(), backup); synchronized (factories) { factories.add(factory); } } private final class StaticConnector implements Serializable { private static final long serialVersionUID = 6772279632415242634L; private List<Connector> connectors; public ClientSessionFactory connect(boolean skipWarnings) throws ActiveMQException { assertOpen(); initialise(); createConnectors(); try { int retryNumber = 0; while (!isClosed()) { retryNumber++; for (Connector conn : connectors) { if (logger.isDebugEnabled()) { logger.debug(this + "::Submitting connect towards " + conn); } ClientSessionFactory csf = conn.tryConnect(); if (csf != null) { csf.getConnection().addFailureListener(new FailureListener() { // Case the node where the cluster connection was connected is gone, we need to restart the // connection @Override public void connectionFailed(ActiveMQException exception, boolean failedOver) { if (clusterConnection && exception.getType() == ActiveMQExceptionType.DISCONNECTED) { try { ServerLocatorImpl.this.start(startExecutor); } catch (Exception e) { // There isn't much to be done if this happens here ActiveMQClientLogger.LOGGER.errorStartingLocator(e); } } } @Override public void connectionFailed(final ActiveMQException me, boolean failedOver, String scaleDownTargetNodeID) { connectionFailed(me, failedOver); } @Override public String toString() { return "FailureListener('restarts cluster connections')"; } }); if (logger.isDebugEnabled()) { logger.debug("Returning " + csf + " after " + retryNumber + " retries on StaticConnector " + ServerLocatorImpl.this); } return csf; } } if (initialConnectAttempts >= 0 && retryNumber > initialConnectAttempts) { break; } if (latch.await(retryInterval, TimeUnit.MILLISECONDS)) return null; } } catch (RejectedExecutionException e) { if (isClosed() || skipWarnings) return null; logger.debug("Rejected execution", e); throw e; } catch (Exception e) { if (isClosed() || skipWarnings) return null; ActiveMQClientLogger.LOGGER.errorConnectingToNodes(e); throw ActiveMQClientMessageBundle.BUNDLE.cannotConnectToStaticConnectors(e); } if (isClosed() || skipWarnings) { return null; } ActiveMQClientLogger.LOGGER.errorConnectingToNodes(traceException); throw ActiveMQClientMessageBundle.BUNDLE.cannotConnectToStaticConnectors2(); } private synchronized void createConnectors() { if (connectors != null) { for (Connector conn : connectors) { if (conn != null) { conn.disconnect(); } } } connectors = new ArrayList<>(); if (initialConnectors != null) { for (TransportConfiguration initialConnector : initialConnectors) { ClientSessionFactoryInternal factory = new ClientSessionFactoryImpl(ServerLocatorImpl.this, initialConnector, callTimeout, callFailoverTimeout, clientFailureCheckPeriod, connectionTTL, retryInterval, retryIntervalMultiplier, maxRetryInterval, reconnectAttempts, threadPool, scheduledThreadPool, incomingInterceptors, outgoingInterceptors); factory.disableFinalizeCheck(); connectors.add(new Connector(initialConnector, factory)); } } } public synchronized void disconnect() { if (connectors != null) { for (Connector connector : connectors) { connector.disconnect(); } } } @Override protected void finalize() throws Throwable { if (!isClosed() && finalizeCheck) { ActiveMQClientLogger.LOGGER.serverLocatorNotClosed(traceException, System.identityHashCode(this)); if (ServerLocatorImpl.finalizeCallback != null) { ServerLocatorImpl.finalizeCallback.run(); } close(); } super.finalize(); } private final class Connector { private final TransportConfiguration initialConnector; private volatile ClientSessionFactoryInternal factory; private Connector(TransportConfiguration initialConnector, ClientSessionFactoryInternal factory) { this.initialConnector = initialConnector; this.factory = factory; } public ClientSessionFactory tryConnect() throws ActiveMQException { if (logger.isDebugEnabled()) { logger.debug(this + "::Trying to connect to " + factory); } try { ClientSessionFactoryInternal factoryToUse = factory; if (factoryToUse != null) { addToConnecting(factoryToUse); try { factoryToUse.connect(1, false); } finally { removeFromConnecting(factoryToUse); } } return factoryToUse; } catch (ActiveMQException e) { logger.debug(this + "::Exception on establish connector initial connection", e); return null; } } public void disconnect() { if (factory != null) { factory.causeExit(); factory.cleanup(); factory = null; } } @Override public String toString() { return "Connector [initialConnector=" + initialConnector + "]"; } } } private void assertOpen() { synchronized (stateGuard) { if (state != null && state != STATE.INITIALIZED) { throw new IllegalStateException("Server locator is closed (maybe it was garbage collected)"); } } } @Override public boolean isClosed() { synchronized (stateGuard) { return state != STATE.INITIALIZED; } } private Object writeReplace() throws ObjectStreamException { ServerLocatorImpl clone = new ServerLocatorImpl(this); return clone; } public boolean isReceivedTopology() { return receivedTopology; } private String fromInterceptors(final List<Interceptor> interceptors) { StringBuffer buffer = new StringBuffer(); boolean first = true; for (Interceptor value : interceptors) { if (!first) { buffer.append(","); } first = false; buffer.append(value.getClass().getName()); } return buffer.toString(); } private void feedInterceptors(final List<Interceptor> interceptors, final String interceptorList) { interceptors.clear(); if (interceptorList == null || interceptorList.trim().equals("")) { return; } AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { String[] arrayInterceptor = interceptorList.split(","); for (String strValue : arrayInterceptor) { Interceptor interceptor = (Interceptor) ClassloadingUtil.newInstanceFromClassLoader(strValue.trim()); interceptors.add(interceptor); } return null; } }); } }