/* * 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.distributed.jgroups; import java.io.InputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import net.sf.hajdbc.distributed.Command; import net.sf.hajdbc.distributed.CommandDispatcher; import net.sf.hajdbc.distributed.CommandResponse; import net.sf.hajdbc.distributed.Member; import net.sf.hajdbc.distributed.MembershipListener; import net.sf.hajdbc.distributed.Stateful; import net.sf.hajdbc.logging.Level; import net.sf.hajdbc.logging.Logger; import net.sf.hajdbc.logging.LoggerFactory; import net.sf.hajdbc.util.ObjectInputStream; import net.sf.hajdbc.util.Objects; import org.jgroups.Address; import org.jgroups.Channel; import org.jgroups.Message; import org.jgroups.MessageListener; import org.jgroups.View; import org.jgroups.blocks.MessageDispatcher; import org.jgroups.blocks.RequestHandler; import org.jgroups.blocks.RequestOptions; import org.jgroups.blocks.ResponseMode; import org.jgroups.util.Rsp; /** * A JGroups-based command dispatcher. * * @author Paul Ferraro * @see org.jgroups.blocks.MessageDispatcher * @param <C> the execution context type */ public class JGroupsCommandDispatcher<C> implements RequestHandler, CommandDispatcher<C>, org.jgroups.MembershipListener, MessageListener { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final String id; private final long timeout; private final MessageDispatcher dispatcher; private final C context; private final AtomicReference<View> viewReference = new AtomicReference<>(); private final MembershipListener membershipListener; private final Stateful stateful; /** * Constructs a new ChannelCommandDispatcher. * @param id the channel name * @param channel a JGroups channel * @param timeout the command timeout * @param context the execution context * @param stateful the state transfer handler * @param membershipListener notified of membership changes * @throws Exception if channel cannot be created */ public JGroupsCommandDispatcher(String id, Channel channel, long timeout, C context, Stateful stateful, MembershipListener membershipListener) throws Exception { this.id = id; this.context = context; this.stateful = stateful; this.membershipListener = membershipListener; this.dispatcher = new MessageDispatcher(channel, this, this, this); this.timeout = timeout; } /** * {@inheritDoc} * @see net.sf.hajdbc.Lifecycle#start() */ @Override public void start() throws SQLException { Channel channel = this.dispatcher.getChannel(); channel.setDiscardOwnMessages(false); // Connect and fetch state try { channel.connect(this.id, null, 0); } catch (Exception e) { throw new SQLException(e); } } /** * {@inheritDoc} * @see net.sf.hajdbc.Lifecycle#stop() */ @Override public void stop() { Channel channel = this.dispatcher.getChannel(); if (channel.isOpen()) { if (channel.isConnected()) { channel.disconnect(); } } } @Override protected void finalize() { this.dispatcher.getChannel().close(); } @Override public <R> Map<Member, CommandResponse<R>> executeAll(Command<R, C> command, Member... excludedMembers) throws Exception { Message message = this.createMessage(null, command); RequestOptions options = this.createRequestOptions(); if ((excludedMembers != null) && (excludedMembers.length > 0)) { Address[] exclusions = new Address[excludedMembers.length]; for (int i = 0; i < excludedMembers.length; ++i) { exclusions[i] = ((AddressMember) excludedMembers[i]).getAddress(); } options.setExclusionList(exclusions); } Map<Address, Rsp<R>> responses = this.dispatcher.castMessage(null, message, options); Map<Member, CommandResponse<R>> results = new TreeMap<>(); for (Map.Entry<Address, Rsp<R>> entry: responses.entrySet()) { Rsp<R> response = entry.getValue(); if (response.wasReceived() && !response.wasSuspected()) { results.put(new AddressMember(entry.getKey()), new RspCommandResponse<>(response)); } } return results; } @Override public <R> CommandResponse<R> execute(Command<R, C> command, Member member) throws Exception { Message message = this.createMessage(((AddressMember) member).getAddress(), command); // Use sendMessageWithFuture(...) instead of sendMessage(...) since we want to differentiate between sender exceptions and receiver exceptions Future<R> future = this.dispatcher.sendMessageWithFuture(message, this.createRequestOptions()); try { return new SimpleCommandResponse<>(future.get()); } catch (InterruptedException e) { return new ExceptionCommandResponse<>(new ExecutionException(e)); } catch (ExecutionException e) { return new ExceptionCommandResponse<>(e); } } private <R> Message createMessage(Address destination, Command<R, C> command) { return new Message(destination, this.getLocalAddress(), Objects.serialize(command)); } private RequestOptions createRequestOptions() { return new RequestOptions(ResponseMode.GET_ALL, this.timeout, false, null, Message.Flag.DONT_BUNDLE, Message.Flag.OOB); } /** * {@inheritDoc} * @see net.sf.hajdbc.distributed.CommandDispatcher#getLocal() */ @Override public AddressMember getLocal() { return new AddressMember(this.getLocalAddress()); } private Address getLocalAddress() { return this.dispatcher.getChannel().getAddress(); } /** * {@inheritDoc} * @see net.sf.hajdbc.distributed.CommandDispatcher#getCoordinator() */ @Override public AddressMember getCoordinator() { Address address = this.getCoordinatorAddress(); return (address != null) ? new AddressMember(address) : null; } private Address getCoordinatorAddress() { List<Address> members = this.dispatcher.getChannel().getView().getMembers(); return members.isEmpty() ? null : members.get(0); } /** * {@inheritDoc} * @see org.jgroups.blocks.RequestHandler#handle(org.jgroups.Message) */ @Override public Object handle(Message message) { Command<Object, C> command = Objects.deserialize(message.getRawBuffer(), Command.class); this.logger.log(Level.DEBUG, "{0} received from {1}", command, message.getSrc()); return command.execute(this.context); } /** * {@inheritDoc} * @see org.jgroups.MembershipListener#viewAccepted(org.jgroups.View) */ @Override public void viewAccepted(View view) { if (this.membershipListener != null) { View oldView = this.viewReference.getAndSet(view); for (Address address: view.getMembers()) { if ((oldView == null) || !oldView.containsMember(address)) { this.membershipListener.added(new AddressMember(address)); } } if (oldView != null) { for (Address address: oldView.getMembers()) { if (!view.containsMember(address)) { this.membershipListener.removed(new AddressMember(address)); } } } } } /** * {@inheritDoc} * @see org.jgroups.MessageListener#getState(java.io.OutputStream) */ @Override public void getState(OutputStream output) throws Exception { ObjectOutput out = new ObjectOutputStream(output); this.stateful.writeState(out); out.flush(); } /** * {@inheritDoc} * @see org.jgroups.MessageListener#setState(java.io.InputStream) */ @Override public void setState(InputStream input) throws Exception { this.stateful.readState(new ObjectInputStream(input, Stateful.class.getClassLoader())); } /** * {@inheritDoc} * @see org.jgroups.MembershipListener#suspect(org.jgroups.Address) */ @Override public void suspect(Address member) { } /** * {@inheritDoc} * @see org.jgroups.MembershipListener#block() */ @Override public void block() { } /** * {@inheritDoc} * @see org.jgroups.MembershipListener#unblock() */ @Override public void unblock() { } /** * {@inheritDoc} * @see org.jgroups.MessageListener#receive(org.jgroups.Message) */ @Override public void receive(Message message) { } private class RspCommandResponse<R> implements CommandResponse<R> { private final Rsp<R> response; public RspCommandResponse(Rsp<R> response) { this.response = response; } @Override public R get() throws ExecutionException { Throwable exception = this.response.getException(); if (exception != null) { throw new ExecutionException(exception); } return this.response.getValue(); } } private static class SimpleCommandResponse<R> implements CommandResponse<R> { private final R result; SimpleCommandResponse(R result) { this.result = result; } @Override public R get() { return this.result; } } private static class ExceptionCommandResponse<R> implements CommandResponse<R> { private final ExecutionException exception; ExceptionCommandResponse(ExecutionException exception) { this.exception = exception; } @Override public R get() throws ExecutionException { throw this.exception; } } }