/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2016 The eXist 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.NotThreadSafe;
import org.exist.util.Configuration;
import com.evolvedbinary.j8fu.fsm.AtomicFSM;
import com.evolvedbinary.j8fu.fsm.FSM;
import static com.evolvedbinary.j8fu.fsm.TransitionTable.transitionTable;
import java.util.ArrayList;
import java.util.List;
/**
* This class simply maintains a list of {@link BrokerPoolService}
* and provides methods to {@BrokerPool} to manage the lifecycle of
* those services.
*
* This class should only be accessed from {@link BrokerPool}
* and the order of method invocation (service state change)
* is significant and must follow the order:
*
* register -> configure -> prepare ->
* system -> pre-multi-user -> multi-user
*
* @author Adam Retter <adam.retter@googlemail.com>
*/
@NotThreadSafe
class BrokerPoolServicesManager {
private enum ManagerState {
REGISTRATION,
CONFIGURATION,
PREPARATION,
SYSTEM,
PRE_MULTI_USER,
MULTI_USER
}
private enum ManagerEvent {
CONFIGURE,
PREPARE,
ENTER_SYSTEM_MODE,
PREPARE_ENTER_MULTI_USER_MODE,
ENTER_MULTI_USER_MODE
}
@SuppressWarnings("unchecked")
private FSM<ManagerState, ManagerEvent> states = new AtomicFSM<>(ManagerState.REGISTRATION, transitionTable(ManagerState.class, ManagerEvent.class)
.when(ManagerState.REGISTRATION)
.on(ManagerEvent.CONFIGURE).switchTo(ManagerState.CONFIGURATION)
.on(ManagerEvent.PREPARE).switchTo(ManagerState.PREPARATION)
.on(ManagerEvent.ENTER_SYSTEM_MODE).switchTo(ManagerState.SYSTEM)
.on(ManagerEvent.PREPARE_ENTER_MULTI_USER_MODE).switchTo(ManagerState.PRE_MULTI_USER)
.on(ManagerEvent.ENTER_MULTI_USER_MODE).switchTo(ManagerState.MULTI_USER)
.build()
);
final List<BrokerPoolService> brokerPoolServices = new ArrayList<>();
/**
* Register a Service to be managed
*
* Note all services must be registered before any service is configured
* failure to do so will result in an {@link IllegalStateException}
*
* @param brokerPoolService The service to be managed
*
* @return The service after it has been registered
*
* @throws IllegalStateException Thrown if there is an attempt to register a service
* after any other service has been configured.
*/
<T extends BrokerPoolService> T register(final T brokerPoolService) {
final ManagerState currentState = states.getCurrentState();
if(currentState != ManagerState.REGISTRATION) {
throw new IllegalStateException(
"Services may only be registered during the registration state. Current state is: " + currentState.name());
}
brokerPoolServices.add(brokerPoolService);
return brokerPoolService;
}
/**
* Configures the Services
*
* Expected to be called from {@link BrokerPool#initialize()}
*
* @param configuration The database configuration (i.e. conf.xml)
*
* @throws BrokerPoolServiceException if any service causes an error during configuration
*
* @throws IllegalStateException Thrown if there is an attempt to configure a service
* after any other service has been prepared.
*/
void configureServices(final Configuration configuration) throws BrokerPoolServiceException {
states.process(ManagerEvent.CONFIGURE);
for(final BrokerPoolService brokerPoolService : brokerPoolServices) {
brokerPoolService.configure(configuration);
}
}
/**
* Prepare the Services for system (single user) mode
*
* Prepare is called before the BrokerPool enters
* system (single user) mode. As yet there are still
* no brokers!
*
* @throws BrokerPoolServiceException if any service causes an error during preparation
*
* @throws IllegalStateException Thrown if there is an attempt to prepare a service
* after any other service has entered start system service.
*/
void prepareServices(final BrokerPool brokerPool) throws BrokerPoolServiceException {
states.process(ManagerEvent.PREPARE);
for(final BrokerPoolService brokerPoolService : brokerPoolServices) {
brokerPoolService.prepare(brokerPool);
}
}
/**
* Starts any services which should be started directly after
* the database enters system mode, but before any system mode
* operations are performed.
*
* At this point the broker pool is in system (single user) mode
* and not generally available for access, only a single
* system broker is available.
*
* @param systemBroker The System Broker which is available for
* services to use to access the database
*
* @throws BrokerPoolServiceException if any service causes an error during starting the system mode
*
* @throws IllegalStateException Thrown if there is an attempt to start a service
* after any other service has entered the start pre-multi-user system mode.
*/
void startSystemServices(final DBBroker systemBroker) throws BrokerPoolServiceException {
states.process(ManagerEvent.ENTER_SYSTEM_MODE);
for(final BrokerPoolService brokerPoolService : brokerPoolServices) {
brokerPoolService.startSystem(systemBroker);
}
}
/**
* Starts any services which should be started directly after
* the database finishes system mode operations, but before
* entering multi-user mode
*
* At this point the broker pool is still in system (single user) mode
* and not generally available for access, only a single
* system broker is available.
*
* @param systemBroker The System Broker which is available for
* services to use to access the database
*
* @throws BrokerPoolServiceException if any service causes an error during starting the pre-multi-user mode
*
* @throws IllegalStateException Thrown if there is an attempt to start pre-multi-user system a service
* after any other service has entered multi-user.
*/
void startPreMultiUserSystemServices(final DBBroker systemBroker) throws BrokerPoolServiceException {
states.process(ManagerEvent.PREPARE_ENTER_MULTI_USER_MODE);
for(final BrokerPoolService brokerPoolService : brokerPoolServices) {
brokerPoolService.startPreMultiUserSystem(systemBroker);
}
}
/**
* Starts any services which should be started once the database
* enters multi-user mode
*
* @param brokerPool The broker pool instance
*
* @throws BrokerPoolServiceException if any service causes an error during starting multi-user mode
*
* @throws IllegalStateException Thrown if there is an attempt to start multi-user a service
* before we have completed pre-multi-user mode
*/
void startMultiUserServices(final BrokerPool brokerPool) throws BrokerPoolServiceException {
states.process(ManagerEvent.ENTER_MULTI_USER_MODE);
for(final BrokerPoolService brokerPoolService : brokerPoolServices) {
brokerPoolService.startMultiUser(brokerPool);
}
}
}