/*
* eXist Open Source Native XML Database
* Copyright (C) 2003-2016 The eXist-db Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.exist.storage;
import net.jcip.annotations.GuardedBy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.util.Configuration;
import org.exist.util.DatabaseConfigurationException;
import com.evolvedbinary.j8fu.function.ConsumerE;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* This abstract class really just contains the static
* methods for {@link BrokerPool} to help us organise the
* code into smaller understandable chunks and reduce the
* complexity when understanding the concurrency
* constraints between one and many BrokerPools
*
* @author Adam Retter <adam@exist-db.org>
* @author Wolfgang Meier <wolfgang@exist-db.org>
* @author Pierrick Brihaye <pierrick.brihaye@free.fr>
*/
abstract class BrokerPools {
private static final Logger LOG = LogManager.getLogger(BrokerPools.class);
private static final ReadWriteLock instancesLock = new ReentrantReadWriteLock();
@GuardedBy("instancesLock") private static final Map<String, BrokerPool> instances = new TreeMap<>();
/**
* The name of a default database instance
*/
public static String DEFAULT_INSTANCE_NAME = "exist";
// register a shutdown hook
static {
try {
Runtime.getRuntime().addShutdownHook(new Thread("BrokerPools-ShutdownHook") {
/**
* Make sure that all instances are cleanly shut down.
*/
@Override
public void run() {
LOG.info("Executing shutdown thread");
BrokerPools.stopAll(true);
}
});
LOG.debug("Shutdown hook registered");
} catch(final IllegalArgumentException e) {
LOG.warn("Shutdown hook already registered");
}
}
/**
* Creates and configures a default database instance and adds it to the available instances.
* Call this before calling {link #getInstance()}.
* If a default database instance already exists, the new configuration is ignored.
*
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
*
* @throws EXistException If the initialization fails.
*
* @Deprecated Use {@link #configure(int, int, Configuration, Optional)}
*/
@Deprecated
public static void configure(final int minBrokers, final int maxBrokers, final Configuration config)
throws EXistException, DatabaseConfigurationException {
configure(DEFAULT_INSTANCE_NAME, minBrokers, maxBrokers, config);
}
/**
* Creates and configures a default database instance and adds it to the available instances.
* Call this before calling {link #getInstance()}.
* If a default database instance already exists, the new configuration is ignored.
*
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
* @param statusObserver Observes the status of this database instance
*
* @throws EXistException If the initialization fails.
*/
public static void configure(final int minBrokers, final int maxBrokers, final Configuration config,
final Optional<Observer> statusObserver) throws EXistException, DatabaseConfigurationException {
configure(DEFAULT_INSTANCE_NAME, minBrokers, maxBrokers, config, statusObserver);
}
/**
* Creates and configures a database instance and adds it to the pool.
* Call this before calling {link #getInstance()}.
* If a database instance with the same name already exists, the new configuration is ignored.
*
* @param instanceName A <strong>unique</strong> name for the database instance.
* It is possible to have more than one database instance (with different configurations
* for example).
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
*
* @throws EXistException If the initialization fails.
*
* @Deprecated Use {@link #configure(String, int, int, Configuration, Optional)}
*/
@Deprecated
public static void configure(final String instanceName, final int minBrokers, final int maxBrokers,
final Configuration config) throws EXistException {
configure(instanceName, minBrokers, maxBrokers, config, Optional.empty());
}
/**
* Creates and configures a database instance and adds it to the pool.
* Call this before calling {link #getInstance()}.
* If a database instance with the same name already exists, the new configuration is ignored.
*
* @param instanceName A <strong>unique</strong> name for the database instance.
* It is possible to have more than one database instance (with different configurations
* for example).
* @param minBrokers The minimum number of concurrent brokers for handling requests on the database instance.
* @param maxBrokers The maximum number of concurrent brokers for handling requests on the database instance.
* @param config The configuration object for the database instance
* @param statusObserver Observes the status of this database instance
*
* @throws EXistException If the initialization fails.
*/
public static void configure(final String instanceName, final int minBrokers, final int maxBrokers,
final Configuration config, final Optional<Observer> statusObserver) throws EXistException {
// optimize for read-concurrency as instances are configured (created) once and used many times
final Lock readLock = instancesLock.readLock();
readLock.lock();
try {
if (instances.containsKey(instanceName)) {
LOG.warn("Database instance '" + instanceName + "' is already configured");
return;
}
} finally {
readLock.unlock();
}
// fallback to probably having to create a new BrokerPool instance
final Lock writeLock = instancesLock.writeLock();
writeLock.lock();
try {
// check again, as another thread may have preempted us since we released the read-lock
if (instances.containsKey(instanceName)) {
LOG.warn("Database instance '" + instanceName + "' is already configured");
return;
}
LOG.debug("Configuring database instance '" + instanceName + "'...");
try {
//Create the instance
final BrokerPool instance = new BrokerPool(instanceName, minBrokers, maxBrokers, config, statusObserver);
//initialize it!
instance.initialize();
//Add it to the list
instances.put(instanceName, instance);
} catch(final Throwable e) {
// Catch all possible issues and report.
LOG.error("Unable to initialize database instance '" + instanceName + "': " + e.getMessage(), e);
final EXistException ee;
if(e instanceof EXistException) {
ee = (EXistException)e;
} else {
ee = new EXistException(e);
}
throw ee;
}
} finally {
writeLock.unlock();
}
}
/**
* Returns whether or not the default database instance is configured.
*
* @return <code>true</code> if it is configured
*/
public static boolean isConfigured() {
return isConfigured(DEFAULT_INSTANCE_NAME);
}
/**
* Returns whether or not a database instance is configured.
*
* @param instanceName The name of the database instance
* @return <code>true</code> if it is configured
*/
public static boolean isConfigured(final String instanceName) {
final Lock readLock = instancesLock.readLock();
readLock.lock();
try {
final BrokerPool instance = instances.get(instanceName);
if (instance == null) {
return false;
} else {
return instance.isInstanceConfigured();
}
} finally {
readLock.unlock();
}
}
/**
* Returns the broker pool for the default database instance (if it is configured).
*
* @return The broker pool of the default database instance
*
* @throws EXistException If the instance is not available (not created, stopped or not configured)
*/
public static BrokerPool getInstance() throws EXistException {
return getInstance(DEFAULT_INSTANCE_NAME);
}
/**
* Returns a broker pool for a database instance.
*
* @param instanceName The name of the database instance
* @return The broker pool
*
* @throws EXistException If the instance is not available (not created, stopped or not configured)
*/
public static BrokerPool getInstance(final String instanceName) throws EXistException {
//Check if there is a database instance with the same id
final Lock readLock = instancesLock.readLock();
readLock.lock();
try {
final BrokerPool instance = instances.get(instanceName);
if (instance != null) {
//TODO : call isConfigured(id) and throw an EXistException if relevant ?
return instance;
} else {
throw new EXistException("Database instance '" + instanceName + "' is not available");
}
} finally {
readLock.unlock();
}
}
static void removeInstance(final String instanceName) {
final Lock writeLock = instancesLock.writeLock();
writeLock.lock();
try {
instances.remove(instanceName);
} finally {
writeLock.unlock();
}
}
public static <E extends Exception> void readInstances(final ConsumerE<BrokerPool, E> reader) throws E {
final Lock readLock = instancesLock.readLock();
readLock.lock();
try {
for (final BrokerPool instance : instances.values()) {
reader.accept(instance);
}
} finally {
readLock.unlock();
}
}
static int instancesCount() {
final Lock readLock = instancesLock.readLock();
readLock.lock();
try {
return instances.size();
} finally {
readLock.unlock();
}
}
/**
* Stops all the database instances. After calling this method, the database instances are
* no longer configured.
*
* @param killed <code>true</code> when invoked by an exiting JVM
*/
public static void stopAll(final boolean killed) {
final Lock writeLock = instancesLock.writeLock();
writeLock.lock();
try {
for (final BrokerPool instance : instances.values()) {
if (instance.isInstanceConfigured()) {
//Shut it down
instance.shutdown(killed);
}
}
// Clear the living instances container : they are all sentenced to death...
assert(instances.size() == 0); // should have all been removed by BrokerPool#shutdown(boolean)
instances.clear();
} finally {
writeLock.unlock();
}
}
}