
com.sun.messaging.jmq.jmsserver.cluster.manager.ClusterManagerImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2000, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 Payara Services Ltd.
* Copyright (c) 2021, 2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.messaging.jmq.jmsserver.cluster.manager;
import java.util.*;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import com.sun.messaging.jmq.io.MQAddress;
import com.sun.messaging.jmq.util.log.*;
import com.sun.messaging.jmq.util.UID;
import com.sun.messaging.jmq.jmsserver.config.*;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.cluster.api.*;
import com.sun.messaging.jmq.jmsserver.core.BrokerMQAddress;
import com.sun.messaging.jmq.jmsserver.core.DestinationList;
import com.sun.messaging.jmq.jmsserver.util.BrokerException;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import org.jvnet.hk2.annotations.Service;
import jakarta.inject.Singleton;
/**
* This class represents the non-ha implementation of ClusterManager. Configuration information is not retrieved from
* the store.
*
*/
@Service(name = "com.sun.messaging.jmq.jmsserver.cluster.manager.ClusterManagerImpl")
@Singleton
public class ClusterManagerImpl implements ClusterManager, ConfigListener {
private static final String DEBUG_ALL_PROP = Globals.IMQ + ".cluster.debug.all";
private static final boolean debug_CLUSTER_ALL = Globals.getConfig().getBooleanProperty(DEBUG_ALL_PROP);
private static boolean DEBUG_CLUSTER_ALL = debug_CLUSTER_ALL;
private static final String DEBUG_LOCK_PROP = Globals.IMQ + ".cluster.debug.lock";
private static final boolean debug_CLUSTER_LOCK = Globals.getConfig().getBooleanProperty(DEBUG_LOCK_PROP);
private static boolean DEBUG_CLUSTER_LOCK = debug_CLUSTER_LOCK;
private static final String DEBUG_TXN_PROP = Globals.IMQ + ".cluster.debug.txn";
private static final boolean debug_CLUSTER_TXN = Globals.getConfig().getBooleanProperty(DEBUG_TXN_PROP);
private static boolean DEBUG_CLUSTER_TXN = debug_CLUSTER_TXN;
private static final String DEBUG_TAKEOVER_PROP = Globals.IMQ + ".cluster.debug.takeover";
private static final boolean debug_CLUSTER_TAKEOVER = Globals.getConfig().getBooleanProperty(DEBUG_TAKEOVER_PROP);
private static boolean DEBUG_CLUSTER_TAKEOVER = debug_CLUSTER_TAKEOVER;
private static final String DEBUG_MSG_PROP = Globals.IMQ + ".cluster.debug.msg";
private static final boolean debug_CLUSTER_MSG = Globals.getConfig().getBooleanProperty(DEBUG_MSG_PROP);
private static boolean DEBUG_CLUSTER_MSG = debug_CLUSTER_MSG;
private static final String DEBUG_CONN_PROP = Globals.IMQ + ".cluster.debug.conn";
private static final boolean debug_CLUSTER_CONN = Globals.getConfig().getBooleanProperty(DEBUG_CONN_PROP);
private static boolean DEBUG_CLUSTER_CONN = debug_CLUSTER_CONN;
private static final String DEBUG_PING_PROP = Globals.IMQ + ".cluster.debug.ping";
private static final boolean debug_CLUSTER_PING = Globals.getConfig().getBooleanProperty(DEBUG_PING_PROP);
private static boolean DEBUG_CLUSTER_PING = debug_CLUSTER_PING;
private static final String DEBUG_PKT_PROP = Globals.IMQ + ".cluster.debug.packet";
private static final boolean debug_CLUSTER_PACKET = Globals.getConfig().getBooleanProperty(DEBUG_PKT_PROP);
private static boolean DEBUG_CLUSTER_PACKET = debug_CLUSTER_PACKET;
/*******************************************************
* accessor methods for dynamic cluster debug flags
*******************************************************/
public static boolean isDEBUG_CLUSTER_ALL() {
return DEBUG_CLUSTER_ALL;
}
public static boolean isDEBUG_CLUSTER_LOCK() {
return DEBUG_CLUSTER_LOCK;
}
public static boolean isDEBUG_CLUSTER_TXN() {
return DEBUG_CLUSTER_TXN;
}
public static boolean isDEBUG_CLUSTER_TAKEOVER() {
return DEBUG_CLUSTER_TAKEOVER;
}
public static boolean isDEBUG_CLUSTER_MSG() {
return DEBUG_CLUSTER_MSG;
}
public static boolean isDEBUG_CLUSTER_CONN() {
return DEBUG_CLUSTER_CONN;
}
public static boolean isDEBUG_CLUSTER_PING() {
return DEBUG_CLUSTER_PING;
}
public static boolean isDEBUG_CLUSTER_PACKET() {
return DEBUG_CLUSTER_PACKET;
}
/**
* Turns on/off debugging.
*/
private static boolean DEBUG = false;
/**
* The basic configuration (name, value pairs) for the broker.
*/
protected BrokerConfig config = Globals.getConfig();
/**
* The class used for logging.
*/
protected Logger logger = Globals.getLogger();
/**
* The set of listeners waiting state or configuration changed information.
*
* @see ClusterListener
*/
protected Set listeners = null;
/**
* The current transport type for the cluster.
*/
protected String transport = null;
/**
* The current hostname used for the cluster service. A value of null indicates bind to all hosts.
*/
protected String clusterhost = null;
/**
* The current port used for the cluster service. A value of 0 indicates that the value should be obtained dynamically.
*/
protected int clusterport = 0;
/**
* This is a private initialization flag.
*/
protected boolean initialized = false;
/**
* This is the brokerid for the local broker.
*/
protected String localBroker = null;
/**
* The brokerid for the master broker, null indicates no master broker.
*/
protected String masterBroker = null;
/**
* The list of all brokers in the cluster. The list contains ClusteredBroker objects.
*
* @see ClusteredBroker
*/
protected Map allBrokers = null;
protected static final String MANUAL_AUTOCONNECT_PROPERTY = Globals.MANUAL_AUTOCONNECT_CLUSTER_PROPERTY;
/**
* Private property name used to set the number of entries in the session list.
*/
protected static final String MAX_OLD_SESSIONS_PROP = Globals.IMQ + ".cluster.maxTakeoverSessions";
protected static final String MAX_RETAITION_TIME_PROP = Globals.IMQ + ".cluster.maxTakeoverSessionRetaintionTime";
protected static final long MAX_RETAITION_TIME_DEFAULT = 1800; // secs
protected int maxTakeoverSessions = Globals.getConfig().getIntProperty(MAX_OLD_SESSIONS_PROP, 10);
protected long maxTakeoverRetaintionTime = getMAX_RETAITION_TIME(); // millisecs
/**
*/
protected LinkedHashMap supportedSessionMap = new LinkedHashMap<>();
/**
* The id of the cluster.
*/
private String clusterid = Globals.getClusterID();
private BrokerResources br = Globals.getBrokerResources();
private MQAddress brokerNextToMe = null;
private Object brokerNextToMeLock = new Object();
private int clusterPingInterval = CLUSTER_PING_INTERVAL_DEFAULT;
private static long getMAX_RETAITION_TIME() {
long t = Globals.getConfig().getLongProperty(MAX_RETAITION_TIME_PROP, MAX_RETAITION_TIME_DEFAULT);
if (t < MAX_RETAITION_TIME_DEFAULT) {
t = MAX_RETAITION_TIME_DEFAULT;
}
return t * 1000L;
}
/**
* Creates a ClusterManagerImpl.
*/
public ClusterManagerImpl() {
listeners = new LinkedHashSet();
}
@Override
public int getClusterPingInterval() {
return clusterPingInterval;
}
/**
* Retrieves the cluster id associated with this cluster.
*
* @return the id or null if this is not an HA cluster
*/
@Override
public String getClusterId() {
return clusterid;
}
/**
* Internal method to return the map which contains all brokers. This method may be overridden by sub-classes.
*/
protected Map initAllBrokers(MQAddress myaddr) throws BrokerException {
return new HashMap();
}
/**
* Changes the host/port of the local broker.
*
* @param address MQAddress to the portmapper
* @throws BrokerException if something goes wrong when the address is changed
*/
@Override
public void setMQAddress(MQAddress address) throws Exception {
if (!initialized) {
initialize(address); // sets up cluster state
} else {
mqAddressChanged(address);
}
}
/**
* Retrieves the host/port of the local broker.
*
* @return the MQAddress to the portmapper
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public MQAddress getMQAddress() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return getLocalBroker().getBrokerURL();
}
/**
* Sets a listener for notification when the state/status or configuration of the cluster changes.
*
*
* This api is used by the Monitor Service to determine when a broker should be monitored because it may be down.
*
* @see ClusterListener
* @param listener the listener to add
*/
@Override
public void addEventListener(ClusterListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* Removes a listener for notification when the state changes.
*
*
* This api is used by the Monitor Service to determine when a broker should be monitored because it may be down.
*
* @return true if the item existed and was removed.
* @see ClusterListener
* @param listener the listener to remove
*/
@Override
public boolean removeEventListener(ClusterListener listener) {
synchronized (listeners) {
return listeners.remove(listener);
}
}
/**
* Retrieves the ClusteredBroker which represents this broker.
*
* @return the local broker
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
* @see ClusterManagerImpl#getBroker(String)
*/
@Override
public ClusteredBroker getLocalBroker() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return getBroker(localBroker);
}
/**
* Returns the current number of brokers in the cluster. In a non-ha cluster, this includes all brokers which have a
* BrokerLink to the local broker and the local broker.
*
* @return count of all brokers in the cluster.
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public int getKnownBrokerCount() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
synchronized (allBrokers) {
return allBrokers.size();
}
}
/**
* Returns the current number of brokers in the configuration propperties. In a non-ha cluster, this includes all
* brokers listed by -cluster or the cluster property.
*
* @return count of configured brokers in the cluster.
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public int getConfigBrokerCount() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
int cnt = 0;
synchronized (allBrokers) {
Iterator itr = allBrokers.values().iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
if (cb.isConfigBroker()) {
cnt++;
}
}
}
return cnt;
}
/**
* Returns the current number of brokers in the cluster. In a non-ha cluster, this includes all brokers which have an
* active BrokerLink to the local broker and the local broker.
*
* @return count of all brokers in the cluster.
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public int getActiveBrokerCount() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
int cnt = 0;
synchronized (allBrokers) {
Iterator itr = allBrokers.values().iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
if (BrokerStatus.getBrokerLinkIsUp(cb.getStatus())) {
cnt++;
}
}
}
return cnt;
}
/**
* Returns an iterator of ClusteredBroker objects for all brokers in the cluster. This is a copy of the current list.
*
* @param refresh if true refresh current list then return it
* @return iterator of ClusteredBrokers
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public Iterator getKnownBrokers(boolean refresh) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
HashSet brokers = null;
synchronized (allBrokers) {
brokers = new HashSet(allBrokers.values());
}
return brokers.iterator();
}
static class ConfigIterator implements Iterator {
Object nextObj = null;
Iterator parent = null;
ConfigIterator(Iterator parentItr) {
parent = parentItr;
}
@Override
public boolean hasNext() {
if (nextObj != null) {
return true;
}
if (!parent.hasNext()) {
return false;
}
while (nextObj == null) {
if (!parent.hasNext()) {
break;
}
nextObj = parent.next();
ClusteredBroker cb = (ClusteredBroker) nextObj;
if (!cb.isConfigBroker()) {
parent.remove();
nextObj = null; // not valid
}
}
return nextObj != null;
}
@Override
public Object next() {
// ok, skip to the right location
if (!hasNext()) {
throw new NoSuchElementException("no more");
}
Object ret = nextObj;
nextObj = null;
return ret;
}
@Override
public void remove() {
parent.remove();
}
}
/**
* Returns an iterator of ClusteredBroker objects for all brokers in the cluster. This is a copy of the current list and
* is accurate at the time getBrokers was called.
*
* @return iterator of ClusteredBrokers
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public Iterator getConfigBrokers() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
HashSet brokers = null;
synchronized (allBrokers) {
brokers = new HashSet(allBrokers.values());
return new ConfigIterator(brokers.iterator());
}
}
static class ActiveIterator implements Iterator
{
Object nextObj = null;
Iterator parent = null;
ActiveIterator(Iterator parentItr) {
parent = parentItr;
}
@Override
public boolean hasNext() {
if (nextObj != null) {
return true;
}
if (!parent.hasNext()) {
return false;
}
while (nextObj == null) {
if (!parent.hasNext()) {
break;
}
nextObj = parent.next();
ClusteredBroker cb = (ClusteredBroker) nextObj;
if (BrokerStatus.getBrokerLinkIsDown(cb.getStatus())) {
parent.remove();
nextObj = null; // not valid
}
}
return nextObj != null;
}
@Override
public Object next() {
// ok, skip to the right location
if (!hasNext()) {
throw new NoSuchElementException("no more");
}
Object ret = nextObj;
nextObj = null;
return ret;
}
@Override
public void remove() {
parent.remove();
}
}
/**
* Returns an iterator of ClusteredBroker objects for all active brokers in the cluster. This is a copy of the current
* list and is accurate at the time getBrokers was called.
*
* @return iterator of ClusteredBrokers
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public Iterator getActiveBrokers() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
HashSet brokers = null;
synchronized (allBrokers) {
brokers = new HashSet(allBrokers.values());
return new ActiveIterator(brokers.iterator());
}
}
/**
* Returns a specific ClusteredBroker object by name.
*
* @param brokerid the id associated with the broker
* @return the broker associated with brokerid or null if the broker is not found
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public ClusteredBroker getBroker(String brokerid) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
synchronized (allBrokers) {
return (ClusteredBroker) allBrokers.get(brokerid);
}
}
/**
* Method used in a dynamic cluster, it updates the system when a new broker is added.
*
* @param URL the MQAddress of the new broker
* @param uid the brokerSessionUID associated with this broker (if known)
* @param instName the instance name of the broker to be activated
* @param userData optional data associated with the status change
* @throws NoSuchElementException if the broker can not be added to the cluster (for example if the cluster is running
* in HA mode and the URL is not in the shared database)
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
* @return the uid associated with the new broker
*/
@Override
public String activateBroker(MQAddress URL, UID uid, String instName, Object userData) throws BrokerException {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
// does it exist yet ?
String brokerid = lookupBrokerID(URL);
if (brokerid == null) {
brokerid = addBroker(URL, false, false, uid);
}
return activateBroker(brokerid, uid, instName, userData);
}
/**
* method used in a all clusters, it updates the system when a new broker is added.
*
* @param brokerid the id of the broker (if known)
* @param uid the broker sessionUID
* @param instName the broker instance name
* @param userData optional data associated with the status change
* @throws NoSuchElementException if the broker can not be added to the cluster (for example if the cluster is running
* in HA mode and the brokerid is not in the shared database)
* @throws BrokerException if the database can not be accessed
* @return the uid associated with the new broker
*/
@Override
public String activateBroker(String brokerid, UID uid, String instName, Object userData) throws BrokerException {
ClusteredBroker cb = getBroker(brokerid);
if (cb == null) {
throw new BrokerException("Unknown broker " + brokerid);
}
cb.setInstanceName(instName);
if (uid != null) {
cb.setBrokerSessionUID(uid);
}
cb = updateBrokerOnActivation(cb, userData);
cb.setStatus(BrokerStatus.ACTIVATE_BROKER, userData);
return brokerid;
}
/**
* protected method used to update a newly activated broker if necessary
*/
protected ClusteredBroker updateBrokerOnActivation(ClusteredBroker broker, Object userData) {
return broker;
}
protected ClusteredBroker updateBrokerOnDeactivation(ClusteredBroker broker, Object userData) {
broker.setInstanceName(null);
return broker;
}
/** @throws NoSuchElementException */
protected String addBroker(MQAddress URL, boolean isLocal, boolean config, UID sid) throws BrokerException {
ClusteredBroker cb = newClusteredBroker(URL, isLocal, sid);
((ClusteredBrokerImpl) cb).setConfigBroker(config);
synchronized (allBrokers) {
allBrokers.put(cb.getBrokerName(), cb);
}
brokerChanged(ClusterReason.ADDED, cb.getBrokerName(), null, cb, sid, null);
return cb.getBrokerName();
}
/**
* only be called internally from cluster manager framework
*/
public ClusteredBroker newClusteredBroker(MQAddress URL, boolean isLocal, UID sid) throws BrokerException {
return new ClusteredBrokerImpl(this, URL, isLocal, sid);
}
protected void removeFromAllBrokers(String brokerName) {
synchronized (allBrokers) {
allBrokers.remove(brokerName);
}
}
/**
* method used in a dynamic cluster, it updates the system when a broker is removed.
*
* @param URL the MQAddress associated with the broker
* @param userData optional data associated with the status change
* @throws NoSuchElementException if the broker can not be found in the cluster.
*/
@Override
public void deactivateBroker(MQAddress URL, Object userData) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
// does it exist yet ?
String brokerid = lookupBrokerID(URL);
if (brokerid == null) {
throw new NoSuchElementException("Unknown URL " + URL);
}
deactivateBroker(brokerid, userData);
}
/**
* Method used in a dynamic cluster, it updates the system when a broker is removed.
*
* @param brokerid the id associated with the broker
* @param userData optional data associated with the status change
* @throws NoSuchElementException if the broker can not be found in the cluster.
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public void deactivateBroker(String brokerid, Object userData) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
ClusteredBroker cb = null;
boolean removed = false;
synchronized (allBrokers) {
cb = (ClusteredBroker) allBrokers.get(brokerid);
if (cb == null) {
throw new NoSuchElementException("Unknown Broker" + brokerid);
}
if (!cb.isConfigBroker()) { // remove it if its dynamic
allBrokers.remove(brokerid);
removed = true;
}
updateBrokerOnDeactivation(cb, null);
}
// OK, set the broker link down
cb.setStatus(BrokerStatus.setBrokerLinkIsDown(cb.getStatus()), userData);
if (removed) {
brokerChanged(ClusterReason.REMOVED, cb.getBrokerName(), cb, null, cb.getBrokerSessionUID(), null);
}
}
/**
* Finds the brokerid associated with the given host/port.
*
* @param broker the MQAddress of the new broker
* @return the id associated with the broker or null if the broker does not exist
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public String lookupBrokerID(MQAddress broker) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
// the safe thing to do is to iterate
synchronized (allBrokers) {
Iterator itr = allBrokers.values().iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
MQAddress addr = cb.getBrokerURL();
if (addr.equals(broker)) {
return cb.getBrokerName();
}
}
}
return null;
}
/**
* finds the brokerid associated with the given session.
*
* @param uid is the session uid to search for
* @return the uid associated with the session or null we cant find it.
*/
@Override
public String lookupStoreSessionOwner(UID uid) {
return null;
}
@Override
public String getStoreSessionCreator(UID uid) {
return null;
}
/**
* finds the brokerid associated with the given session.
*
* @param uid is the session uid to search for
* @return the uid associated with the session or null we cant find it.
*/
@Override
public String lookupBrokerSessionUID(UID uid) {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
// the safe thing to do is to iterate
synchronized (allBrokers) {
Iterator itr = allBrokers.values().iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
UID buid = cb.getBrokerSessionUID();
if (buid.equals(uid)) {
return cb.getBrokerName();
}
}
}
return null;
}
/**
* @return true if allow configured master broker
*/
protected boolean allowMasterBroker() {
return true;
}
/**
* The master broker in the cluster (if any).
*
* @return the master broker (or null if none)
* @see ClusterManagerImpl#getBroker(String)
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public ClusteredBroker getMasterBroker() {
if (masterBroker == null) {
return null;
}
return getBroker(masterBroker);
}
/**
* The transport (as a string) used by the cluster of brokers.
*
* @return the transport (tcp, ssl)
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public String getTransport() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return transport;
}
/**
* Returns the port configured for the cluster service.
*
* @return the port (or 0 if a dynamic port should be used)
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public int getClusterPort() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return clusterport;
}
/**
* Returns the host that the cluster service should bind to .
*
* @return the hostname (or null if the service should bind to all)
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public String getClusterHost() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return clusterhost;
}
/**
* Is the cluster "highly available" ?
*
* @return true if the cluster is HA
* @throws RuntimeException if the cluster has not be initialized (which occurs the first time the MQAddress is set)
* @see ClusterManagerImpl#setMQAddress
* @see Globals#getHAEnabled()
*/
@Override
public boolean isHA() {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
return false;
}
/**
* Reload cluster properties from config
*
*/
@Override
public void reloadConfig() throws BrokerException {
if (!initialized) {
throw new RuntimeException("Cluster not initialized");
}
String[] props = { CLUSTERURL_PROPERTY, AUTOCONNECT_PROPERTY }; // XXX
config.reloadProps(Globals.getConfigName(), props, false);
}
protected void setupListeners() {
config.addListener(TRANSPORT_PROPERTY, this);
config.addListener(HOST_PROPERTY, this);
config.addListener(PORT_PROPERTY, this);
config.addListener(AUTOCONNECT_PROPERTY, this);
config.addListener(CONFIG_SERVER, this);
config.addListener(CLUSTER_PING_INTERVAL_PROP, this);
}
/**
* Initializes the cluster (loading all configuration). This methods is called the first time setMQAddress is called
* after the broker is created.
*
* @param address the address of the local broker
* @throws BrokerException if the cluster can not be initialized
* @see ClusterManagerImpl#setMQAddress
*/
@Override
public String initialize(MQAddress address) throws BrokerException {
initialized = true;
allBrokers = initAllBrokers(address);
setupListeners();
config.addListener(DEBUG_ALL_PROP, this);
config.addListener(DEBUG_LOCK_PROP, this);
config.addListener(DEBUG_TXN_PROP, this);
config.addListener(DEBUG_TAKEOVER_PROP, this);
config.addListener(DEBUG_MSG_PROP, this);
config.addListener(DEBUG_CONN_PROP, this);
config.addListener(DEBUG_PING_PROP, this);
config.addListener(DEBUG_PKT_PROP, this);
// handle parsing transport
transport = config.getProperty(TRANSPORT_PROPERTY);
if (transport == null) {
transport = "tcp";
}
clusterhost = config.getProperty(HOST_PROPERTY);
// if not set, try imq.hostname
if (clusterhost == null) {
clusterhost = Globals.getHostname();
if (clusterhost != null && clusterhost.equals(Globals.HOSTNAME_ALL)) {
clusterhost = null;
}
}
clusterport = config.getIntProperty(PORT_PROPERTY, 0);
clusterPingInterval = config.getIntProperty(CLUSTER_PING_INTERVAL_PROP, CLUSTER_PING_INTERVAL_DEFAULT);
if (clusterPingInterval <= 0) {
clusterPingInterval = CLUSTER_PING_INTERVAL_DEFAULT;
}
LinkedHashSet s = null;
try {
s = parseBrokerList();
} catch (Exception ex) {
logger.logStack(Logger.ERROR, Globals.getBrokerResources().getKString(BrokerResources.X_BAD_ADDRESS_BROKER_LIST, ex.toString()), ex);
throw new BrokerException(ex.getMessage(), ex);
}
localBroker = addBroker(address, true, s.remove(address), new UID());
getLocalBroker().setStatus(BrokerStatus.ACTIVATE_BROKER, null);
setBrokerNextToMe(s);
// handle broker list
Iterator itr = s.iterator();
while (itr.hasNext()) {
MQAddress addr = (MQAddress) itr.next();
try {
// ok, are we the local broker ?
ClusteredBroker lcb = getLocalBroker();
if (addr.equals(getMQAddress())) {
if (lcb instanceof ClusteredBrokerImpl) {
((ClusteredBrokerImpl) lcb).setConfigBroker(true);
}
} else {
addBroker(addr, false, true, null);
}
} catch (NoSuchElementException ex) {
logger.logStack(Logger.ERROR, ex.getMessage() + ": " + "bad address in the broker list ", ex);
}
}
// handle master broker
String mbroker = config.getProperty(CONFIG_SERVER);
if (!allowMasterBroker()) {
if (DEBUG || logger.getLevel() <= Logger.DEBUG) {
logger.log(Logger.INFO, "This broker does not allow " + CONFIG_SERVER + " to be configured."
+ (mbroker == null ? "" : " Ignore " + CONFIG_SERVER + "=" + mbroker));
}
mbroker = null;
} else if (Globals.useSharedConfigRecord()) {
if (mbroker == null) {
logger.log(logger.INFO, br.getKString(br.I_USE_SHARECC_STORE));
} else {
logger.log(logger.WARNING, br.getKString(br.I_USE_SHARECC_STORE_IGNORE_MB, CONFIG_SERVER + "=" + mbroker));
}
mbroker = null;
}
if (mbroker != null) {
// ok, see if we exist
MQAddress addr = null;
try {
addr = BrokerMQAddress.createAddress(mbroker);
} catch (Exception ex) {
logger.logStack(Logger.ERROR, BrokerResources.E_INTERNAL_BROKER_ERROR, "bad address while parsing " + "the broker list ", ex);
}
masterBroker = lookupBrokerID(addr);
if (masterBroker == null) { // wasnt in list, add it
logger.log(Logger.WARNING, BrokerResources.W_MB_UNSET, addr.toString());
masterBroker = addBroker(addr, false, true, null);
}
masterBroker = lookupBrokerID(addr);
}
if (DEBUG) {
logger.log(Logger.DEBUG, "Cluster is:" + toString());
}
return localBroker;
}
/**
* Method which determines the list of brokers in the cluster. For non-ha clusters, this is determined by the
* configuration properties for this broker.
*
* @return a Set containing all MQAddress objects associated with the broker cluster (except the local broker)
* @throws MalformedURLException if something is wrong with the properties and an MQAddress can not be created.
*/
protected LinkedHashSet parseBrokerList() throws MalformedURLException, UnknownHostException {
// OK, handles the broker list removing
/*
* "imq.cluster.brokerlist" is usually kept in the cluster configuration file. Administrators can use this list to setup
* a 'permanent' set of brokers that will join this cluster.
*/
String propfileSetting = config.getProperty(AUTOCONNECT_PROPERTY);
String cmdlineSetting = config.getProperty(MANUAL_AUTOCONNECT_PROPERTY);
String values = null;
if (propfileSetting == null && cmdlineSetting == null) {
return new LinkedHashSet();
}
if (propfileSetting == null) {
values = cmdlineSetting;
} else if (cmdlineSetting == null) {
values = propfileSetting;
} else {
values = cmdlineSetting + "," + propfileSetting;
}
return parseBrokerList(values);
}
@Override
public LinkedHashSet parseBrokerList(String values) throws MalformedURLException, UnknownHostException {
// we want to pull out dups .. so we use a hashmap
// with host:port as a key
HashMap tmpMap = new LinkedHashMap();
// OK, parse properties
StringTokenizer st = new StringTokenizer(values, ",");
// Parse the given broker address list.
while (st.hasMoreTokens()) {
String s = st.nextToken();
MQAddress address = BrokerMQAddress.createAddress(s);
tmpMap.put(address.toString(), address);
}
// OK, we can now return the list of MQAddresses
return new LinkedHashSet(tmpMap.values());
}
private void setBrokerNextToMe(LinkedHashSet list) {
synchronized (brokerNextToMeLock) {
Iterator itr = list.iterator();
MQAddress addr = null, next = null;
boolean foundlocal = false;
int i = 0;
while (itr.hasNext()) {
addr = (MQAddress) itr.next();
if (i == 0) {
if (!addr.equals(getMQAddress())) {
next = addr;
}
}
if (foundlocal) {
next = addr;
break;
}
if (addr.equals(getMQAddress())) {
foundlocal = true;
}
}
brokerNextToMe = next;
}
}
@Override
public MQAddress getBrokerNextToMe() {
synchronized (brokerNextToMeLock) {
return brokerNextToMe;
}
}
/**
* Returns a user-readable string representing this class.
*
* @return the user-readable represeation.
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("ClusterManager: [local=").append(localBroker).append(", master = ").append(masterBroker).append("]\n");
synchronized (allBrokers) {
Iterator itr = allBrokers.values().iterator();
while (itr.hasNext()) {
str.append('\t').append(itr.next()).append('\n');
}
}
return str.toString();
}
/**
* Gets the UID associated with the local broker
*
* @return null (this cluster type does not support session)
*/
@Override
public synchronized UID getStoreSessionUID() {
return null;
}
/**
* Gets the UID associated with the local broker
*
* @return null (this cluster type does not support session)
*/
@Override
public synchronized UID getBrokerSessionUID() {
return getLocalBroker().getBrokerSessionUID();
}
/**
* Adds an old UID to the list of supported sessions for this broker.
*
* @param uid the broker's store session UID that has been taken over
*/
protected void addSupportedStoreSessionUID(UID uid) {
synchronized (supportedSessionMap) {
supportedSessionMap.put(uid, Long.valueOf(System.currentTimeMillis()));
}
}
/**
* Returns a list of supported session UID's for this broker (not including its own sessionUID).
*
* This list may not include all sessionUID's that have been supported by this running broker (ids may age out over
* time).
*
*
* @return the set of sessionUIDs
*/
@Override
public Set getSupportedStoreSessionUIDs() {
Set s = null;
synchronized (supportedSessionMap) {
s = new HashSet(supportedSessionMap.keySet());
}
if (getStoreSessionUID() != null) {
s.add(getStoreSessionUID());
}
return s;
}
/**
* Handles reparsing the broker list if it changes.
*
* @throws BrokerException if something goes wrong during parsing
*/
protected void brokerListChanged() throws BrokerException {
// OK .. get the new broker list
LinkedHashSet s = null;
try {
s = parseBrokerList();
setBrokerNextToMe(s);
if (DEBUG) {
logger.log(Logger.INFO, "ClusterManagerImpl.parseBrokerList:" + s);
}
} catch (Exception ex) {
logger.logStack(Logger.ERROR, BrokerResources.E_INTERNAL_BROKER_ERROR, "bad address in brokerListChanged ", ex);
s = new LinkedHashSet();
}
Iterator itr = s.iterator();
while (itr.hasNext()) {
MQAddress addr = (MQAddress) itr.next();
if (lookupBrokerID(addr) == null) {
addBroker(addr, false, true, null);
}
}
// OK, we need to clean up the allBroker's list
List oldBrokers = new ArrayList();
synchronized (allBrokers) {
itr = allBrokers.values().iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
((ClusteredBrokerImpl) cb).setConfigBroker(true);
MQAddress addr = cb.getBrokerURL();
if (s.contains(addr)) {
s.remove(addr);
continue;
} else if (!cb.isLocalBroker()) {
oldBrokers.add(cb);
itr.remove();
}
}
}
// send out remove notifications
itr = oldBrokers.iterator();
while (itr.hasNext()) {
ClusteredBroker cb = (ClusteredBroker) itr.next();
brokerChanged(ClusterReason.REMOVED, cb.getBrokerName(), cb, null, cb.getBrokerSessionUID(), null);
itr.remove();
}
// now add any remaining brokers
itr = s.iterator();
while (itr.hasNext()) {
addBroker((MQAddress) itr.next(), false, true, null);
}
}
/**
* Handles changing the name of the master broker.
*
* @param mbroker the brokerid associated with the master broker
* @throws BrokerException if something goes wrong
*/
protected void masterBrokerChanged(String mbroker) throws BrokerException {
// handle master broker
ClusteredBroker oldMaster = getMasterBroker();
masterBroker = null;
if (mbroker != null) {
// ok, see if we exist
MQAddress addr = null;
try {
addr = BrokerMQAddress.createAddress(mbroker);
} catch (Exception ex) {
logger.log(Logger.ERROR, BrokerResources.W_BAD_MB, mbroker, ex);
}
masterBroker = lookupBrokerID(addr);
if (masterBroker == null) { // wasnt in list, add it
masterBroker = addBroker(addr, false, true, null);
}
}
ClusteredBroker newMaster = getMasterBroker();
brokerChanged(ClusterReason.MASTER_BROKER_CHANGED, null, oldMaster, newMaster, null, null);
}
/**
* Method called when the MQAddress is changed on the system.
*
* @param address the new address of the local brokers portmapper
*/
protected void mqAddressChanged(MQAddress address) throws Exception {
ClusteredBroker cb = getLocalBroker();
MQAddress oldAddress = cb.getBrokerURL();
cb.setBrokerURL(address);
brokerChanged(ClusterReason.ADDRESS_CHANGED, cb.getBrokerName(), oldAddress, address, null, null);
}
/**
* Validates an updated property.
*
* @see ConfigListener
* @param name the name of the property to be changed
* @param value the new value of the property
* @throws PropertyUpdateException if the value is invalid (e.g. format is wrong, property can not be changed)
*/
@Override
public void validate(String name, String value) throws PropertyUpdateException {
if (name.equals(TRANSPORT_PROPERTY)) {
// XXX - is there a valid value
throw new PropertyUpdateException(br.getString(br.X_BAD_PROPERTY, name));
} else if (name.equals(HOST_PROPERTY)) {
// nothing to validate
throw new PropertyUpdateException(br.getString(br.X_BAD_PROPERTY, name));
} else if (name.equals(PORT_PROPERTY)) {
// validate its an int
try {
Integer.parseInt(value);
} catch (NumberFormatException ex) {
throw new PropertyUpdateException(PORT_PROPERTY + " should be set to an int" + " not " + value);
}
} else if (name.equals(CLUSTER_PING_INTERVAL_PROP)) {
try {
int v = Integer.parseInt(value);
if (v <= 0) {
throw new NumberFormatException("" + value);
}
} catch (NumberFormatException ex) {
throw new PropertyUpdateException(CLUSTER_PING_INTERVAL_PROP + " should be set to a positive int" + " not " + value);
}
} else if (name.equals(AUTOCONNECT_PROPERTY)) {//NOPMD
// XXX - is there a valid value
} else if (name.equals(CONFIG_SERVER)) {
try {
BrokerMQAddress.createAddress(value);
} catch (Exception e) {
throw new PropertyUpdateException(br.getString(br.X_BAD_PROPERTY, value) + ": " + e.getMessage());
}
}
}
/**
* Updates a new configuration property.
*
* @see ConfigListener
* @param name the name of the property to be changed
* @param value the new value of the property
* @return true if the property took affect immediately, false if the broker needs to be restarted.
*/
@Override
public boolean update(String name, String value) {
if (name.equals(TRANSPORT_PROPERTY)) {
transport = value;
if (transport == null || transport.length() == 0) {
transport = "tcp";
}
clusterPropertyChanged(name, value);
} else if (name.equals(HOST_PROPERTY)) {
clusterPropertyChanged(name, value);
} else if (name.equals(PORT_PROPERTY)) {
clusterPropertyChanged(name, value);
} else if (name.equals(CLUSTER_PING_INTERVAL_PROP)) {
clusterPropertyChanged(name, value);
} else if (name.equals(AUTOCONNECT_PROPERTY)) {
if (DEBUG) {
logger.log(logger.INFO, "ClusterManagerImpl.update(" + name + "=" + value + ")");
}
try {
brokerListChanged();
} catch (Exception ex) {
logger.log(Logger.INFO, "INTERNAL ERROR", ex);
}
} else if (name.equals(CONFIG_SERVER)) {
try {
masterBrokerChanged(value);
} catch (Exception ex) {
logger.log(Logger.INFO, "INTERNAL ERROR", ex);
}
} else if (name.equals(DEBUG_ALL_PROP)) {
DEBUG_CLUSTER_ALL = Boolean.valueOf(value);
DEBUG_CLUSTER_LOCK = Boolean.valueOf(value);
DEBUG_CLUSTER_TXN = Boolean.valueOf(value);
DEBUG_CLUSTER_TAKEOVER = Boolean.valueOf(value);
DEBUG_CLUSTER_MSG = Boolean.valueOf(value);
DEBUG_CLUSTER_CONN = Boolean.valueOf(value);
DEBUG_CLUSTER_PING = Boolean.valueOf(value);
DEBUG_CLUSTER_PACKET = Boolean.valueOf(value);
} else if (name.equals(DEBUG_LOCK_PROP)) {
DEBUG_CLUSTER_LOCK = Boolean.valueOf(value);
} else if (name.equals(DEBUG_TXN_PROP)) {
DEBUG_CLUSTER_TXN = Boolean.valueOf(value);
} else if (name.equals(DEBUG_TAKEOVER_PROP)) {
DEBUG_CLUSTER_TAKEOVER = Boolean.valueOf(value);
} else if (name.equals(DEBUG_MSG_PROP)) {
DEBUG_CLUSTER_MSG = Boolean.valueOf(value);
} else if (name.equals(DEBUG_CONN_PROP)) {
DEBUG_CLUSTER_CONN = Boolean.valueOf(value);
} else if (name.equals(DEBUG_PING_PROP)) {
DEBUG_CLUSTER_PING = Boolean.valueOf(value);
} else if (name.equals(DEBUG_PKT_PROP)) {
DEBUG_CLUSTER_PACKET = Boolean.valueOf(value);
}
return true;
}
/**
* Called to notify ClusterListeners when the cluster service configuration. Configuration changes include:
*
* - cluster service port
* - cluster service hostname
* - cluster service transport
*
*
* @param name the name of the changed property
* @param value the new value of the changed property
* @see ClusterListener
*/
public void clusterPropertyChanged(String name, String value) {
synchronized (listeners) {
if (listeners.size() == 0) {
return;
}
Iterator itr = listeners.iterator();
while (itr.hasNext()) {
ClusterListener listen = (ClusterListener) itr.next();
listen.clusterPropertyChanged(name, value);
}
}
}
/**
* only to be called internally from cluster manager framework
*
* Notify ClusterListeners when state of the cluster is changed. Reasons for changes include:
*
* - A broker has been added to the cluster
* - A broker has been removed from the cluster
* - the master broker has changed
* - the portmapper address has changed
* - the protocol version of a broker has changed (this should only happen when a broker reconnects to the
* cluster)
* - the dynamic status of the broker has changed
* - the state of the broker has changed
*
*
* The data passed to the listener is determined by the reason this method is being called:
*
*
* Reason
* brokerid
* oldvalue
* newvalue
*
*
* ADDED
* added broker
* null
* ClusteredBroker added
*
*
* REMOVED
* removed broker
* ClusteredBroker removed
* null
*
*
* STATUS_CHANGED
* changed broker
* Integer (old status)
* Integer (new status)
*
*
* STATE_CHANGED
* changed broker
* BrokerState (old state)
* BrokerState (new state)
*
*
* VERSION_CHANGED
* changed broker
* Integer (old version)
* Integer (new version)
*
*
* ADDRESS_CHANGED
* changed broker
* MQAddress (old address)
* MQAddress (new address)
*
*
* MASTER_BROKER_CHANGED
* null
* old master (ClusteredBroker)
* new master (ClusteredBroker)
* the master broker has changed
*
*
*
* @param reason why this listener is being called
* @param brokerid broker affected (if applicable)
* @param oldvalue old value if applicable
* @param newvalue new value if applicable
* @param userData optional user data (if applicable)
* @see ClusterListener
*/
public void brokerChanged(ClusterReason reason, String brokerid, Object oldvalue, Object newvalue, UID suid, Object userData) {
synchronized (listeners) {
if (listeners.size() == 0) {
return;
}
}
BrokerChangedEntry bce = new BrokerChangedEntry(reason, brokerid, oldvalue, newvalue, suid, userData);
// OK, if we are processing, queue up next entry
//
synchronized (brokerChangedEntryList) {
brokerChangedEntryList.add(bce);
if (brokerChangedProcessing == true)
{
return; // let other guy handle it
}
brokerChangedProcessing = true;
}
try {
BrokerChangedEntry process = null;
while (true) {
ClusterListener[] alisteners = null;
synchronized (listeners) {
synchronized (brokerChangedEntryList) {
if (listeners.size() == 0 || brokerChangedEntryList.isEmpty()) {
// nothing to do
brokerChangedProcessing = false;
break;
}
process = (BrokerChangedEntry) brokerChangedEntryList.removeFirst();
}
alisteners = (ClusterListener[]) listeners.toArray(new ClusterListener[listeners.size()]);
}
for (int i = 0; i < alisteners.length; i++) {
ClusterListener listen = alisteners[i];
synchronized (listeners) {
if (!listeners.contains(listen)) {
continue;
}
}
if (process.reason == ClusterReason.ADDED) {
listen.brokerAdded((ClusteredBroker) process.newValue, process.brokerSession);
} else if (process.reason == ClusterReason.REMOVED) {
listen.brokerRemoved((ClusteredBroker) process.oldValue, process.brokerSession);
} else if (process.reason == ClusterReason.STATUS_CHANGED) {
listen.brokerStatusChanged(process.brokerid, ((Integer) process.oldValue).intValue(), ((Integer) process.newValue).intValue(),
process.brokerSession, process.userData);
} else if (process.reason == ClusterReason.STATE_CHANGED) {
listen.brokerStateChanged(process.brokerid, (BrokerState) process.oldValue, (BrokerState) process.newValue);
} else if (process.reason == ClusterReason.VERSION_CHANGED) {
listen.brokerVersionChanged(process.brokerid, ((Integer) process.oldValue).intValue(), ((Integer) process.newValue).intValue());
} else if (process.reason == ClusterReason.ADDRESS_CHANGED) {
listen.brokerURLChanged(process.brokerid, (MQAddress) process.oldValue, (MQAddress) process.newValue);
} else if (process.reason == ClusterReason.MASTER_BROKER_CHANGED) {
listen.masterBrokerChanged((ClusteredBroker) process.oldValue, (ClusteredBroker) process.newValue);
}
}
}
} finally {
synchronized (brokerChangedEntryList) {
brokerChangedProcessing = false;
}
}
}
/**
* flag used to determine if we are in the middle of listener processing when a call occurs (to make sure notifications
* are processed in order).
*/
private boolean brokerChangedProcessing = false;
/**
* list used to make sure listeners are processed in order
*/
LinkedList brokerChangedEntryList = new LinkedList();
/**
* container class used when listeners are processed in order.
*/
private static class BrokerChangedEntry {
ClusterReason reason = null;
String brokerid = null;
Object oldValue = null;
Object newValue = null;
Object userData = null;
UID brokerSession = null;
BrokerChangedEntry(ClusterReason reason, String brokerid, Object oldValue, Object newValue, UID bs, Object userData) {
this.reason = reason;
this.brokerid = brokerid;
this.oldValue = oldValue;
this.newValue = newValue;
this.userData = userData;
this.brokerSession = bs;
}
}
// NOTE: for clustered brokers, the id is really
// the same as the host:port
int brokerindx = 0;
@Override
public ClusteredBroker getBrokerByNodeName(String nodeName) throws BrokerException {
throw new UnsupportedOperationException("Unexpected call: " + getClass().getName() + ".getbrokerByNodeName()");
}
/**
* @param partitionID the partition id
*/
@Override
public void partitionAdded(UID partitionID, Object source) {
synchronized (supportedSessionMap) {
if (source instanceof DestinationList) {
supportedSessionMap.put(partitionID, Long.valueOf(System.currentTimeMillis()));
}
}
}
/**
* @param partitionID the partition id
*/
@Override
public void partitionRemoved(UID partitionID, Object source, Object destinedTo) {
synchronized (supportedSessionMap) {
if (Globals.getDestinationList().isPartitionMode()) {
if (partitionID != null) {
supportedSessionMap.remove(partitionID);
}
return;
}
if (partitionID != null) {
Long v = supportedSessionMap.get(partitionID);
if (v != null) {
supportedSessionMap.put(partitionID, Long.valueOf(0L));
}
}
if (supportedSessionMap.size() > maxTakeoverSessions) {
Iterator> itr = supportedSessionMap.entrySet().iterator();
long currtime = System.currentTimeMillis();
while (itr.hasNext()) {
Long v = itr.next().getValue();
if (v.longValue() == 0L || (currtime - v.longValue()) > maxTakeoverRetaintionTime) {
itr.remove();
break;
}
}
}
}
}
}