/** * Copyright (c) 2002-2012 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j 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.neo4j.cluster.protocol.atomicbroadcast; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.neo4j.cluster.protocol.snapshot.Snapshot; import org.neo4j.cluster.protocol.snapshot.SnapshotProvider; import org.slf4j.LoggerFactory; /** * Map that is synced through an Atomic Broadcast protocol */ public class AtomicBroadcastMap<K,V> implements Map<K,V> { private Map<K,V> map = new ConcurrentHashMap<K, V>( ); private AtomicBroadcast atomicBroadcast; private volatile MapCommand lastCommand; protected final AtomicBroadcastListener atomicBroadcastListener; private final AtomicBroadcastSerializer serializer = new AtomicBroadcastSerializer(); public AtomicBroadcastMap( AtomicBroadcast atomicBroadcast, Snapshot snapshot ) { snapshot.setSnapshotProvider( new SnapshotProvider() { @Override public void getState( ObjectOutputStream output ) throws IOException { output.writeObject( new HashMap( map) ); } @Override public void setState( ObjectInputStream input ) throws IOException, ClassNotFoundException { map = new ConcurrentHashMap((Map<K, V>) input.readObject()); LoggerFactory.getLogger( getClass() ).info( "Updated snapshot state:" + map ); } } ); this.atomicBroadcast = atomicBroadcast; atomicBroadcastListener = new AtomicBroadcastListener() { @Override public void receive( Payload value ) { try { MapCommand command = (MapCommand) serializer.receive( value); command.execute( map ); // LoggerFactory.getLogger( getClass() ).info( "Map:"+map ); synchronized( AtomicBroadcastMap.this ) { if (command.equals( lastCommand )) { lastCommand = null; AtomicBroadcastMap.this.notifyAll(); } } } catch( IOException e ) { e.printStackTrace(); } catch( ClassNotFoundException e ) { e.printStackTrace(); } } }; atomicBroadcast.addAtomicBroadcastListener( atomicBroadcastListener ); } @Override public int size() { checkUpToDate(); return map.size(); } @Override public boolean isEmpty() { checkUpToDate(); return map.isEmpty(); } @Override public boolean containsKey( Object key ) { checkUpToDate(); return map.containsKey( key ); } @Override public boolean containsValue( Object value ) { checkUpToDate(); return map.containsValue( value ); } @Override public V get( Object key ) { LoggerFactory.getLogger(getClass()).info( "GET "+(lastCommand != null ? lastCommand.toString () : "")); checkUpToDate(); return map.get( key ); } @Override public V put( K key, V value ) { try { atomicBroadcast.broadcast( serializer.broadcast(lastCommand = new Put( key, value ) )); LoggerFactory.getLogger(getClass()).info("PUT "+lastCommand); return map.get( key ); } catch( IOException e ) { throw new IllegalStateException( e ); } } @Override public V remove( Object key ) { try { atomicBroadcast.broadcast( serializer.broadcast(lastCommand = new Remove( key ) )); return map.get( key ); } catch( IOException e ) { throw new IllegalStateException( e ); } } @Override public void putAll( Map<? extends K, ? extends V> m ) { try { atomicBroadcast.broadcast( serializer.broadcast(lastCommand = new PutAll( m ) )); } catch( IOException e ) { throw new IllegalStateException( e ); } } @Override public void clear() { try { atomicBroadcast.broadcast( serializer.broadcast( lastCommand = new Clear() )); } catch( IOException e ) { throw new IllegalStateException( e ); } } @Override public Set<K> keySet() { checkUpToDate(); return map.keySet(); } @Override public Collection<V> values() { checkUpToDate(); return map.values(); } @Override public Set<Entry<K,V>> entrySet() { checkUpToDate(); return map.entrySet(); } public void close() { checkUpToDate(); atomicBroadcast.removeAtomicBroadcastListener( atomicBroadcastListener ); } private synchronized void checkUpToDate() { while (lastCommand != null) { LoggerFactory.getLogger(getClass()).info("Wait for command:"+lastCommand); try { this.wait(5000); } catch( InterruptedException e ) { e.printStackTrace(); } } } public interface MapCommand { void execute(Map map); } public static class Put implements Serializable, MapCommand { Object key; Object value; public Put( Object key, Object value ) { this.key = key; this.value = value; } @Override public void execute( Map map ) { map.put( key, value ); } @Override public boolean equals( Object o ) { if( this == o ) { return true; } if( o == null || getClass() != o.getClass() ) { return false; } Put put = (Put) o; if( !key.equals( put.key ) ) { return false; } if( !value.equals( put.value ) ) { return false; } return true; } @Override public int hashCode() { int result = key.hashCode(); result = 31 * result + value.hashCode(); return result; } @Override public String toString() { return "Put: "+key+"="+value; } } public static class Remove implements Serializable, MapCommand { Object key; public Remove( Object key ) { this.key = key; } @Override public void execute( Map map ) { map.remove( key ); } @Override public boolean equals( Object o ) { if( this == o ) { return true; } if( o == null || getClass() != o.getClass() ) { return false; } Remove remove = (Remove) o; if( !key.equals( remove.key ) ) { return false; } return true; } @Override public int hashCode() { return key.hashCode(); } } public static class Clear implements Serializable, MapCommand { @Override public void execute( Map map ) { map.clear(); } @Override public boolean equals( Object o ) { if( this == o ) { return true; } if( o == null || getClass() != o.getClass() ) { return false; } return true; } } public static class PutAll implements Serializable, MapCommand { Map map; public PutAll( Map map ) { this.map = map; } @Override public void execute( Map map ) { map.putAll( this.map ); } @Override public boolean equals( Object o ) { if( this == o ) { return true; } if( o == null || getClass() != o.getClass() ) { return false; } PutAll putAll = (PutAll) o; if( !map.equals( putAll.map ) ) { return false; } return true; } @Override public int hashCode() { return map.hashCode(); } } }