/* * jPOS Project [http://jpos.org] * Copyright (C) 2000-2017 jPOS Software SRL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.jpos.space; import java.io.*; import java.util.Set; import java.util.Map; import java.util.List; import java.util.UUID; import org.jpos.iso.ISOUtil; import org.jpos.util.Log; import org.jpos.util.Logger; import org.jpos.util.LogEvent; import org.jgroups.Channel; import org.jgroups.JChannel; import org.jgroups.View; import org.jgroups.Message; import org.jgroups.Address; import org.jgroups.Receiver; import org.jgroups.conf.XmlConfigurator; @SuppressWarnings("unchecked") public class ReplicatedSpace extends Log implements LocalSpace, Receiver { Channel channel; String nodeName; String nodePrefix; String seqName; Space sp; TSpace sl; View view; boolean trace; boolean replicate; public static final long TIMEOUT = 15000L; public static final long MAX_WAIT = 1000L; public static final long MAX_OUT_WAIT = 5000L; public static final long ONE_MINUTE = 60000L; public static final long FIVE_MINUTES = 5*60000L; private static final long NRD_RESOLUTION = 500L; public ReplicatedSpace ( Space sp, String groupName, String configFile, Logger logger, String realm, boolean trace, boolean replicate) throws Exception { super (); this.sp = sp; setLogger (logger, realm); initChannel(groupName, configFile); this.nodeName = channel.getAddress().toString(); this.nodePrefix = nodeName + "."; this.seqName = nodeName + ".seq"; this.trace = trace; this.replicate = replicate; } public ReplicatedSpace (Space sp, String groupName, String configFile) throws Exception { this (sp, groupName, configFile, null, null, false, false); } public void close() throws IOException { block(); channel.close(); } public void out(Object key, Object value) { out(key, value, 0L); } public void out (Object key, Object value, long timeout) { getCoordinator(); try { Request r = new Request (Request.OUT, key, value, timeout); channel.send (new Message (null, null, r)); Object o = sp.in (r.getUUID(), MAX_OUT_WAIT); if (o == null) throw new SpaceError ("Could not out " + key); } catch (Exception e) { throw new SpaceError (e); } } public void push (Object key, Object value) { push(key, value, 0L); } public void push (Object key, Object value, long timeout) { Address coordinator = getCoordinator(); try { Request r = new Request (Request.PUSH, key, value, timeout); channel.send (new Message (null, null, r)); Object o = sp.in (r.getUUID(), MAX_OUT_WAIT); if (o == null) throw new SpaceError ("Could not push " + key); } catch (Exception e) { throw new SpaceError (e); } } public void put (Object key, Object value) { put(key, value, 0L); } public void put (Object key, Object value, long timeout) { getCoordinator(); try { Request r = new Request (Request.PUT, key, value, timeout); channel.send (new Message (null, null, r)); Object o = sp.in (r.getUUID(), MAX_OUT_WAIT); if (o == null) throw new SpaceError ("Could not put " + key); } catch (Exception e) { throw new SpaceError (e); } } public Object rdp (Object key) { Request r = new Request (Request.RDP, key, 0); r.value = r.getUUID(); sendToCoordinator (r); Object obj = sp.in (r.value, MAX_WAIT); if (obj instanceof NullPointerException) obj = null; return obj; } public Object inp (Object key) { Request r = new Request (Request.INP, key, 0); r.value = r.getUUID(); sendToCoordinator (r); Object obj = sp.in (r.value, MAX_WAIT); if (obj instanceof NullPointerException) obj = null; return obj; } public void receive (Message msg) { LogEvent evt = null; Object obj = msg.getObject(); if (trace && logger != null) { evt = createTrace (" receive: " + msg.toString()); if (obj != null) { evt.addMessage (" object: " + obj.toString()); } } if (obj instanceof Request) { Request r = (Request) obj; switch (r.type) { case Request.OUT: if (r.timeout != 0) sp.out (r.key, r.value, r.timeout + TIMEOUT); else sp.out (r.key, r.value); if (msg.getSrc().equals (channel.getAddress())) { sp.out (r.getUUID(), Boolean.TRUE, MAX_OUT_WAIT); } if (sl != null) notifyListeners(r.key, r.value); break; case Request.PUSH: if (r.timeout != 0) sp.push (r.key, r.value, r.timeout + TIMEOUT); else sp.push (r.key, r.value); if (msg.getSrc().equals (channel.getAddress())) { sp.out (r.getUUID(), Boolean.TRUE, MAX_OUT_WAIT); } if (sl != null) notifyListeners(r.key, r.value); break; case Request.PUT: if (r.timeout != 0) sp.put (r.key, r.value, r.timeout + TIMEOUT); else sp.put (r.key, r.value); if (msg.getSrc().equals (channel.getAddress())) { sp.out (r.getUUID(), Boolean.TRUE, MAX_OUT_WAIT); } if (sl != null) notifyListeners(r.key, r.value); break; case Request.RDP: send (msg.getSrc(), new Request ( Request.RDP_RESPONSE, r.value, // value is ref key for response sp.rdp (r.key) ) ); break; case Request.RDP_RESPONSE: if (r.value == null) { r.value = new NullPointerException(); if (evt != null) evt.addMessage (" negative response"); } sp.out (r.key, r.value, MAX_WAIT); break; case Request.INP: Object v = sp.inp (r.key); if (v != null) { send (null, new Request ( Request.INP_NOTIFICATION, r.key, new MD5Template (r.key, v) ) ); } send (msg.getSrc(), new Request ( Request.INP_RESPONSE, r.value, // value is ref key for response v ) ); break; case Request.INP_RESPONSE: if (r.value == null) r.value = new NullPointerException(); sp.out (r.key, r.value, MAX_WAIT); break; case Request.INP_NOTIFICATION: // if not self notification if (!channel.getAddress().equals (msg.getSrc())) sp.inp (r.value); break; case Request.SPACE_COPY: if (replicate && !isCoordinator() && sp instanceof TSpace) { ((TSpace)sp).setEntries ((Map) r.value); synchronized (sp) { sp.notifyAll(); } } break; } } else if (evt != null) { evt.addMessage (" class: " + obj.getClass().getName()); } if (evt != null) Logger.log (evt); } /** * Allows an application to write a state through a provided OutputStream. After the state has * been written the OutputStream doesn't need to be closed as stream closing is automatically * done when a calling thread returns from this callback. * * @param output the OutputStream * @throws Exception if the streaming fails, any exceptions should be thrown so that the state requester * can re-throw them and let the caller know what happened * @see OutputStream#close() */ @Override public void getState(OutputStream output) throws Exception { } /** * Allows an application to read a state through a provided InputStream. After the state has been * read the InputStream doesn't need to be closed as stream closing is automatically done when a * calling thread returns from this callback. * * @param input the InputStream * @throws Exception if the streaming fails, any exceptions should be thrown so that the state requester * can catch them and thus know what happened * @see InputStream#close() */ @Override public void setState(InputStream input) throws Exception { } public boolean existAny (Object[] keys) { for (int i=0; i<keys.length; i++) { if (rdp (keys[i]) != null) return true; } return false; } public boolean existAny (Object[] keys, long timeout) { long now = System.currentTimeMillis(); long end = now + timeout; while (((now = System.currentTimeMillis()) < end)) { if (existAny (keys)) return true; try { synchronized (sp) { if (!sp.existAny (keys, timeout)) sp.wait (end - now); } } catch (InterruptedException e) { } } return false; } // ---------------------------------------------------------------- public Object rd (Object key) { Object obj; while ((obj = rdp (key)) == null) { synchronized (sp) { try { if (sp.rdp (key) == null) sp.wait (MAX_WAIT); } catch (InterruptedException e) { } } } return obj; } public Object rd (Object key, long timeout) { Object obj; long end = System.currentTimeMillis() + timeout; while ((obj = rdp (key)) == null) { long timeleft = end - System.currentTimeMillis(); if (timeleft > 0) { synchronized (sp) { try { if (sp.rdp (key) == null) sp.wait (Math.min (MAX_WAIT, timeleft)); } catch (InterruptedException e) { } } } else break; } return obj; } public Object in (Object key) { Object obj; while ((obj = inp(key)) == null) { synchronized (sp) { try { if (sp.rdp (key) == null) sp.wait (MAX_WAIT); } catch (InterruptedException e) { } } } return obj; } public Object in (Object key, long timeout) { Object obj; long end = System.currentTimeMillis() + timeout; while ((obj = inp(key)) == null) { long timeleft = end - System.currentTimeMillis(); if (timeleft > 0) { synchronized (sp) { try { if (sp.rdp (key) == null) sp.wait (Math.min (MAX_WAIT, timeleft)); } catch (InterruptedException e) { } } } else break; } return obj; } public void setTrace (boolean trace) { this.trace = trace; } public boolean isTrace() { return trace; } private Address getCoordinator () { assertChannel(); if (view != null) return view.getMembers().get(0); throw new SpaceError ("Channel not ready - coordinator is null"); } private void assertChannel () { if (!channel.isConnected()) throw new SpaceError ("Channel is not connected"); } /** Called when a member is suspected */ public void suspect (Address suspected_mbr) { // } /** Block sending and receiving of messages until ViewAccepted is called */ public void block () { this.view = null; } /** * Called <em>after</em> the FLUSH protocol has unblocked previously blocked senders, and * messages can be sent again. This callback only needs to be implemented if we require a * notification of that. * <p> * Note that during new view installation we provide guarantee that unblock invocation strictly * follows view installation at some node A belonging to that view . However, some other message * M may squeeze in between view and unblock callbacks. * <p> * For more details see https://jira.jboss.org/jira/browse/JGRP-986 */ @Override public void unblock() { } public void viewAccepted (View view) { this.view = view; if (logger != null) { LogEvent evt = createInfo ("view-accepted"); evt.addMessage (view.toString()); Logger.log (evt); } if (replicate && isCoordinator() && view.getMembers().size() > 1 && sp instanceof TSpace) { new Thread () { public void run() { info ("New node joined, sending full Space"); send (null, new Request ( Request.SPACE_COPY, null, // value is ref key for response ((TSpace)sp).getEntries() ) ); } }.start(); } } public boolean isCoordinator () { return channel.getAddress().equals (view.getMembers().get(0)); } public void setState(byte[] new_state) { // } public void setReplicate (boolean replicate) { this.replicate = replicate; } public boolean isReplicate () { return replicate; } public byte[] getState() { return "DummyState".getBytes(); } private void commitOff() { if (sp instanceof JDBMSpace) ((JDBMSpace)sp).setAutoCommit(false); } private void commitOn() { if (sp instanceof JDBMSpace) ((JDBMSpace)sp).setAutoCommit(true); } private void send (Address destination, Request r) { try { channel.send (new Message (destination, null, r)); } catch (Exception e) { error (e); } } private void sendToCoordinator (Request r) { while (true) { Address coordinator = getCoordinator(); try { channel.send (new Message (coordinator, null, r)); break; } catch (Exception e) { error ("error " + e.getMessage() + ", retrying"); try { Thread.sleep (MAX_WAIT); } catch (InterruptedException ex) { } } } } private void initChannel (String groupName, String configFile) throws Exception { InputStream config = new FileInputStream (configFile); XmlConfigurator conf = XmlConfigurator.getInstance (config); String props = conf.getProtocolStackString(); channel = new JChannel (props); // channel.setOpt(Channel.GET_STATE_EVENTS, Boolean.TRUE); // channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE); channel.setReceiver(this); channel.connect (groupName); info ("member: " + channel.getAddress().toString()); } public static class Request implements Serializable { static final long serialVersionUID = -5676486343295850374L; static final int OUT=1; static final int PUSH=2; static final int RDP=3; static final int RDP_RESPONSE=4; static final int INP=5; static final int INP_RESPONSE=6; static final int INP_NOTIFICATION=7; static final int SPACE_COPY=8; static final int PUT=9; static final String[] types = { "", "OUT", "PUSH", "RDP", "RDP_RESPONSE", "INP", "INP_RESPONSE", "INP_NOTIFICATION", "SPACE_COPY", "PUT" }; public int type=0; public Object key=null; public Object value=null; public long timeout=0; private UUID uuid; public Request() { super(); uuid = UUID.randomUUID(); } public Request(int type, Object key, Object value, long timeout) { this (); this.type = type; this.key = key; this.value = value; this.timeout = timeout; } public Request(int type, Object key, Object value) { this (); this.type = type; this.key = key; this.value = value; } public String toString() { StringBuffer sb=new StringBuffer(); sb.append (type2String (type)); if(key != null) { sb.append(" key=" + key); } if(value != null) { if (value instanceof byte[]) sb.append (" value=" + ISOUtil.hexString ((byte[]) value)); else sb.append(" value=" + value); } sb.append (" timeout=" + timeout); return sb.toString(); } public UUID getUUID () { return uuid; } String type2String (int type) { return type < types.length ? types [type] : "invalid"; } } public Set getKeySet () { return ((LocalSpace)sp).getKeySet(); } public int size (Object key) { return ((LocalSpace)sp).size(key); } public synchronized void addListener (Object key, SpaceListener listener) { getSL().out (key, listener); } public synchronized void addListener (Object key, SpaceListener listener, long timeout) { getSL().out (key, listener, timeout); } public synchronized void removeListener (Object key, SpaceListener listener) { if (sl != null) { sl.inp (new ObjectTemplate (key, listener)); } } public void notifyListeners (final Object key, final Object value) { new Thread() { public void run() { Object[] listeners = null; synchronized (this) { if (sl == null) return; List l = (List) sl.entries.get (key); if (l != null) listeners = l.toArray(); } if (listeners != null) { for (int i=0; i<listeners.length; i++) { Object o = listeners[i]; if (o instanceof SpaceListener) ((SpaceListener) o).notify (key, value); } } } }.start(); } private TSpace getSL() { synchronized (this) { if (sl == null) sl = new TSpace(); } return sl; } public synchronized void nrd (Object key) { while (rdp (key) != null) { try { this.wait (NRD_RESOLUTION); } catch (InterruptedException ignored) { } } } public synchronized Object nrd (Object key, long timeout) { Object obj; long now = System.currentTimeMillis(); long end = now + timeout; while ((obj = rdp (key)) != null && (now = System.currentTimeMillis()) < end) { try { this.wait (Math.min(NRD_RESOLUTION, end - now)); } catch (InterruptedException ignored) { } } return obj; } }