/*
* HA-JDBC: High-Availability JDBC
* Copyright (C) 2012 Paul Ferraro
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package net.sf.hajdbc.state.distributed;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.sf.hajdbc.Database;
import net.sf.hajdbc.DatabaseCluster;
import net.sf.hajdbc.distributed.Command;
import net.sf.hajdbc.distributed.CommandDispatcher;
import net.sf.hajdbc.distributed.CommandDispatcherFactory;
import net.sf.hajdbc.distributed.CommandResponse;
import net.sf.hajdbc.distributed.Member;
import net.sf.hajdbc.distributed.MembershipListener;
import net.sf.hajdbc.distributed.Remote;
import net.sf.hajdbc.distributed.Stateful;
import net.sf.hajdbc.durability.InvocationEvent;
import net.sf.hajdbc.durability.InvokerEvent;
import net.sf.hajdbc.logging.Level;
import net.sf.hajdbc.logging.Logger;
import net.sf.hajdbc.logging.LoggerFactory;
import net.sf.hajdbc.messages.Messages;
import net.sf.hajdbc.messages.MessagesFactory;
import net.sf.hajdbc.state.DatabaseEvent;
import net.sf.hajdbc.state.StateManager;
/**
* @author Paul Ferraro
*/
public class DistributedStateManager<Z, D extends Database<Z>> implements StateManager, StateCommandContext<Z, D>, MembershipListener, Stateful, Remote
{
private final Messages messages = MessagesFactory.getMessages();
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final DatabaseCluster<Z, D> cluster;
private final StateManager stateManager;
private final CommandDispatcher<StateCommandContext<Z, D>> dispatcher;
private final ConcurrentMap<Member, Map<InvocationEvent, Map<String, InvokerEvent>>> remoteInvokerMap = new ConcurrentHashMap<>();
public DistributedStateManager(DatabaseCluster<Z, D> cluster, CommandDispatcherFactory dispatcherFactory) throws Exception
{
this.cluster = cluster;
this.stateManager = cluster.getStateManager();
StateCommandContext<Z, D> context = this;
this.dispatcher = dispatcherFactory.createCommandDispatcher(cluster.getId() + ".state", context, this, this);
}
@Override
public Member getMember()
{
return this.dispatcher.getLocal();
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.StateManager#getActiveDatabases()
*/
@Override
public Set<String> getActiveDatabases()
{
return this.stateManager.getActiveDatabases();
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.StateManager#setActiveDatabases(java.util.Set)
*/
@Override
public void setActiveDatabases(Set<String> databases)
{
this.stateManager.setActiveDatabases(databases);
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.DatabaseClusterListener#activated(net.sf.hajdbc.state.DatabaseEvent)
*/
@Override
public void activated(DatabaseEvent event)
{
this.stateManager.activated(event);
this.execute(new ActivationCommand<Z, D>(event));
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.DatabaseClusterListener#deactivated(net.sf.hajdbc.state.DatabaseEvent)
*/
@Override
public void deactivated(DatabaseEvent event)
{
this.stateManager.deactivated(event);
this.execute(new DeactivationCommand<Z, D>(event));
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.durability.DurabilityListener#afterInvocation(net.sf.hajdbc.durability.InvocationEvent)
*/
@Override
public void afterInvocation(InvocationEvent event)
{
this.stateManager.afterInvocation(event);
this.execute(new PostInvocationCommand<Z, D>(this.getRemoteDescriptor(event)));
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.durability.DurabilityListener#afterInvoker(net.sf.hajdbc.durability.InvokerEvent)
*/
@Override
public void afterInvoker(InvokerEvent event)
{
this.stateManager.afterInvoker(event);
this.execute(new InvokerCommand<Z, D>(this.getRemoteDescriptor(event)));
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.durability.DurabilityListener#beforeInvocation(net.sf.hajdbc.durability.InvocationEvent)
*/
@Override
public void beforeInvocation(InvocationEvent event)
{
this.stateManager.beforeInvocation(event);
this.execute(new PreInvocationCommand<Z, D>(this.getRemoteDescriptor(event)));
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.durability.DurabilityListener#beforeInvoker(net.sf.hajdbc.durability.InvokerEvent)
*/
@Override
public void beforeInvoker(InvokerEvent event)
{
this.stateManager.beforeInvoker(event);
this.execute(new InvokerCommand<Z, D>(this.getRemoteDescriptor(event)));
}
private <R> void execute(Command<R, StateCommandContext<Z, D>> command)
{
try
{
Map<Member, CommandResponse<R>> responses = this.dispatcher.executeAll(command, this.dispatcher.getLocal());
for (Map.Entry<Member, CommandResponse<R>> entry: responses.entrySet())
{
Member member = entry.getKey();
try
{
entry.getValue().get();
}
catch (Exception e)
{
this.logger.log(Level.WARN, e, "Failed to execute {0} on {1}", command, member);
}
}
}
catch (Exception e)
{
this.logger.log(Level.WARN, e, "Failed to send {0} to cluster", command);
}
}
private RemoteInvocationDescriptor getRemoteDescriptor(InvocationEvent event)
{
return new RemoteInvocationDescriptorImpl(event, this.dispatcher.getLocal());
}
private RemoteInvokerDescriptor getRemoteDescriptor(InvokerEvent event)
{
return new RemoteInvokerDescriptorImpl(event, this.dispatcher.getLocal());
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.Lifecycle#start()
*/
@Override
public void start() throws SQLException
{
this.stateManager.start();
this.dispatcher.start();
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.Lifecycle#stop()
*/
@Override
public void stop()
{
this.dispatcher.stop();
this.stateManager.stop();
}
@Override
public boolean isEnabled()
{
return this.stateManager.isEnabled() && this.dispatcher.getLocal().equals(this.dispatcher.getCoordinator());
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.distributed.StateCommandContext#getDatabaseCluster()
*/
@Override
public DatabaseCluster<Z, D> getDatabaseCluster()
{
return this.cluster;
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.distributed.StateCommandContext#getLocalStateManager()
*/
@Override
public StateManager getLocalStateManager()
{
return this.stateManager;
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.distributed.StateCommandContext#getRemoteInvokers(net.sf.hajdbc.distributed.Remote)
*/
@Override
public Map<InvocationEvent, Map<String, InvokerEvent>> getRemoteInvokers(Remote remote)
{
return this.remoteInvokerMap.get(remote.getMember());
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.distributed.Stateful#readState(java.io.ObjectInput)
*/
@Override
public void readState(ObjectInput input) throws IOException
{
if (input.available() > 0)
{
Set<String> databases = new TreeSet<>();
int size = input.readInt();
for (int i = 0; i < size; ++i)
{
databases.add(input.readUTF());
}
this.logger.log(Level.INFO, this.messages.initialClusterState(this.cluster, databases, this.dispatcher.getCoordinator()));
this.stateManager.setActiveDatabases(databases);
}
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.distributed.Stateful#writeState(java.io.ObjectOutput)
*/
@Override
public void writeState(ObjectOutput output) throws IOException
{
Set<D> databases = this.cluster.getBalancer();
output.writeInt(databases.size());
for (D database: databases)
{
output.writeUTF(database.getId());
}
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.distributed.MembershipListener#added(net.sf.hajdbc.distributed.Member)
*/
@Override
public void added(Member member)
{
this.remoteInvokerMap.putIfAbsent(member, new HashMap<InvocationEvent, Map<String, InvokerEvent>>());
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.distributed.MembershipListener#removed(net.sf.hajdbc.distributed.Member)
*/
@Override
public void removed(Member member)
{
if (this.dispatcher.getLocal().equals(this.dispatcher.getCoordinator()))
{
Map<InvocationEvent, Map<String, InvokerEvent>> invokers = this.remoteInvokerMap.remove(member);
if (invokers != null)
{
this.cluster.getDurability().recover(invokers);
}
}
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.state.StateManager#recover()
*/
@Override
public Map<InvocationEvent, Map<String, InvokerEvent>> recover()
{
return this.stateManager.recover();
}
private static class RemoteDescriptor implements Remote, Serializable
{
private static final long serialVersionUID = 3717630867671175936L;
private final Member member;
RemoteDescriptor(Member member)
{
this.member = member;
}
@Override
public Member getMember()
{
return this.member;
}
}
private static class RemoteInvocationDescriptorImpl extends RemoteDescriptor implements RemoteInvocationDescriptor
{
private static final long serialVersionUID = 7782082258670023082L;
private final InvocationEvent event;
RemoteInvocationDescriptorImpl(InvocationEvent event, Member member)
{
super(member);
this.event = event;
}
@Override
public InvocationEvent getEvent()
{
return this.event;
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return this.event.toString();
}
}
private static class RemoteInvokerDescriptorImpl extends RemoteDescriptor implements RemoteInvokerDescriptor
{
private static final long serialVersionUID = 6991831573393882786L;
private final InvokerEvent event;
RemoteInvokerDescriptorImpl(InvokerEvent event, Member member)
{
super(member);
this.event = event;
}
@Override
public InvokerEvent getEvent()
{
return this.event;
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return this.event.toString();
}
}
}