ControlledFailoverMicroSpace.java

package org.microspace.space;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.microspace.failover.FailoverState;
import org.microspace.failover.FailoverStateListener;
import org.microspace.util.MicroLogger;
import org.microspace.util.UniqueId;

/**
 * A failover microspace with local failover control.
 * 
 * @author Gaspar Sinai {@literal gaspar.sinai@microspace.org}
 * @version 2018-11-27
 */
public class ControlledFailoverMicroSpace extends MicroSpaceDelegator implements FailoverMicroSpace, ChainableFailoverMicroSpace
{
	
	FailoverState failoverStateOverride;
	FailoverState failoverStateDelegate;
	
	final FailoverMicroSpace failoverSpaceDelegate;
	final LocalFailoverStateListener listener;
	protected final Lock failoverLock = new ReentrantLock ();
	
	List<FailoverStateListener> listeners = new LinkedList<FailoverStateListener>();
	
	MicroLogger log = new MicroLogger (ControlledFailoverMicroSpace.class);

	public ControlledFailoverMicroSpace(FailoverMicroSpace space) {
		this(space, null);
	}
	public ControlledFailoverMicroSpace(FailoverMicroSpace space, UniqueId spaceId) {
		super(space, spaceId);
		this.failoverStateOverride = FailoverState.INITIALIZING;
		this.failoverSpaceDelegate = space;
		this.listener = new LocalFailoverStateListener();
		failoverSpaceDelegate.addFailoverStateListener(listener);
		this.failoverStateDelegate = failoverSpaceDelegate.getFailoverState();
	}
	/**
	 * {@inheritDoc}
	 */
	public FailoverMicroSpace getBottomLevelFailoverMicroSpace() {
		if (failoverSpaceDelegate instanceof ChainableFailoverMicroSpace) {
			return ((ChainableFailoverMicroSpace) failoverSpaceDelegate).getBottomLevelFailoverMicroSpace();
		}
		return failoverSpaceDelegate;
	}

	@Override
	public FailoverState getFailoverState() {
		return getFailoverState(failoverStateDelegate, failoverStateOverride);
	}
	
	@Override
	public void addFailoverStateListener(FailoverStateListener failoverStateListener) {
		listeners.add(failoverStateListener);
	}

	@Override
	public void removeFailoverStateListener(FailoverStateListener failoverStateListener) {
		listeners.remove(failoverStateListener);
	}

	@Override
	public void start() {
		failoverSpaceDelegate.start();
	}

	@Override
	public void shutdown() {
		failoverSpaceDelegate.removeFailoverStateListener(listener);
		failoverSpaceDelegate.shutdown();
	}
	
	private FailoverState getFailoverState(FailoverState underlyingState, FailoverState overrideState) {
		switch (overrideState) {
		case BACKUP:
			if (underlyingState == FailoverState.INITIALIZING) {
				return FailoverState.INITIALIZING;
			}
			return overrideState;
		case INITIALIZING:
			return overrideState;
		case PRIMARY:
			return underlyingState;
		default:
			break;
		}
		return null;
	}
	
	private void fireFailoverStateChangeBefore (FailoverState before, FailoverState after) {
		for (FailoverStateListener l : listeners) {
			try {
				l.failoverStateChangeBefore(before, after);
			} catch (Throwable e){
				log.error("Failover Listener failed", e);
			}
		}
	}
	private void fireFailoverStateChangeAfter (FailoverState before, FailoverState after) {
		for (FailoverStateListener l : listeners) {
			try {
				l.failoverStateChangeAfter(before, after);
			} catch (Throwable e){
				log.error("Failover Listener failed", e);
			}
		}
	}
	
	public void changeFailoveState (FailoverState newState) {
		if (newState == FailoverState.INITIALIZING) {
			throw new IllegalArgumentException("Can not change to INITIALIZING");
		}
		failoverLock.lock();
		try {
			FailoverState beforeOverride = getFailoverState(failoverStateDelegate, failoverStateOverride);
			FailoverState afterOverride = getFailoverState(failoverStateDelegate, newState);
			if (beforeOverride != afterOverride) {
				fireFailoverStateChangeBefore(beforeOverride, afterOverride);
			}
			failoverStateOverride = newState;
			if (beforeOverride != afterOverride) {
				fireFailoverStateChangeAfter(beforeOverride, afterOverride);
			}
		} finally {
			failoverLock.unlock();
		}
	}

	private class LocalFailoverStateListener implements FailoverStateListener {

		@Override
		public void failoverStateChangeBefore(FailoverState before, FailoverState after) {
			failoverLock.lock();
			try {
				FailoverState beforeOverride = getFailoverState(before, failoverStateOverride);
				FailoverState afterOverride = getFailoverState(after, failoverStateOverride);
				if (beforeOverride != afterOverride) {
					fireFailoverStateChangeBefore(beforeOverride, afterOverride);
				}
				failoverStateDelegate = after;
			} finally {
				failoverLock.unlock();
			}
		}

		@Override
		public void failoverStateChangeAfter(FailoverState before, FailoverState after) {
			failoverLock.lock();
			try {
				failoverStateDelegate = after;
				FailoverState beforeOverride = getFailoverState(before, failoverStateOverride);
				FailoverState afterOverride = getFailoverState(after, failoverStateOverride);
				if (beforeOverride != afterOverride) {
					fireFailoverStateChangeAfter(beforeOverride, afterOverride);
				}
			} finally {
				failoverLock.unlock();
			}
		}
	}
}