/**
* Copyright (c) 2002-2011 "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.ha;
import java.io.File;
import java.io.PrintStream;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Ignore;
import org.neo4j.com.Protocol;
import org.neo4j.kernel.HighlyAvailableGraphDatabase;
import org.neo4j.kernel.ha.Broker;
import org.neo4j.kernel.ha.FakeMasterBroker;
import org.neo4j.kernel.ha.FakeSlaveBroker;
import org.neo4j.kernel.ha.MasterClient;
import org.neo4j.kernel.ha.zookeeper.ZooKeeperException;
import org.neo4j.management.HighAvailability;
import org.neo4j.test.SubProcess;
import slavetest.AbstractHaTest;
import slavetest.Job;
import slavetest.PlaceHolderGraphDatabaseService;
@Ignore
public class StandaloneDatabase
{
private final Controller process;
public static StandaloneDatabase withDefaultBroker( String testMethodName, File path,
int machineId, final LocalhostZooKeeperCluster zooKeeper, String haServer,
String[] extraArgs )
{
return new StandaloneDatabase( testMethodName, new Bootstrap( path, machineId,//
HighlyAvailableGraphDatabase.CONFIG_KEY_HA_SERVER, haServer,//
HighlyAvailableGraphDatabase.CONFIG_KEY_HA_ZOO_KEEPER_SERVERS,
zooKeeper.getConnectionString() )
{
@Override
HighlyAvailableGraphDatabase start( String storeDir, Map<String, String> config )
{
HighlyAvailableGraphDatabase db = new HighlyAvailableGraphDatabase( storeDir,
config );
System.out.println( "Started HA db (w/ zoo keeper)" );
return db;
}
} )
{
@Override
IllegalStateException format( StartupFailureException e )
{
return e.format( zooKeeper );
}
};
}
public static StandaloneDatabase withFakeBroker( String testMethodName, final File path,
int machineId, final int masterId, String[] extraArgs )
{
StandaloneDatabase standalone = new StandaloneDatabase( testMethodName, new Bootstrap(
path, machineId )
{
@Override
HighlyAvailableGraphDatabase start( String storeDir, Map<String, String> config )
{
final PlaceHolderGraphDatabaseService placeHolderGraphDb = new PlaceHolderGraphDatabaseService( path.getAbsolutePath() );
final Broker broker;
if ( machineId == masterId )
{
broker = new FakeMasterBroker( machineId, placeHolderGraphDb );
}
else
{
broker = new FakeSlaveBroker( new MasterClient( "localhost",
Protocol.PORT, placeHolderGraphDb ), masterId, machineId, placeHolderGraphDb );
}
HighlyAvailableGraphDatabase db = new HighlyAvailableGraphDatabase( storeDir, config,
AbstractHaTest.wrapBrokerAndSetPlaceHolderDb( placeHolderGraphDb, broker ) );
placeHolderGraphDb.setDb( db );
System.out.println( "Started HA db (w/o zoo keeper)" );
return db;
}
} );
standalone.awaitStarted();
return standalone;
}
private StandaloneDatabase( String testMethodName, Bootstrap bootstrap )
{
process = new HaDbProcess( testMethodName ).start( bootstrap );
}
@Override
public String toString()
{
return getClass().getSimpleName() + process;
}
public void awaitStarted()
{
try
{
process.awaitStarted();
}
catch ( StartupFailureException e )
{
throw format( e );
}
}
public <T> T executeJob( Job<T> job ) throws Exception
{
try
{
return process.executeJob( job );
}
catch ( StartupFailureException e )
{
throw format( e );
}
}
public int getMachineId()
{
try
{
return process.getMachineId();
}
catch ( StartupFailureException e )
{
throw format( e );
}
}
public void pullUpdates()
{
try
{
process.pullUpdates();
}
catch ( StartupFailureException e )
{
throw format( e );
}
}
IllegalStateException format( StartupFailureException e )
{
return e.format();
}
public void shutdown()
{
SubProcess.stop( process );
}
// <IMPLEMENTATION>
public interface Controller
{
void pullUpdates() throws StartupFailureException;
void awaitStarted() throws StartupFailureException;
int getMachineId() throws StartupFailureException;
<T> T executeJob( Job<T> job ) throws Exception;
}
public static class StartupFailureException extends Exception
{
private final long timestamp;
StartupFailureException( Throwable cause )
{
super( cause );
timestamp = new Date().getTime();
}
public IllegalStateException format()
{
return new IllegalStateException( message() , getCause() );
}
private String message()
{
return "database failed to start @ " + TimestampStream.format( new Date( timestamp ) );
}
IllegalStateException format( LocalhostZooKeeperCluster zooKeeper )
{
Throwable cause = getCause();
String message = message();
if ( cause instanceof ZooKeeperException )
{
message += ". ZooKeeper status: " + zooKeeper.getStatus();
}
return new IllegalStateException( message, cause );
}
}
public static abstract class Bootstrap implements Serializable
{
private final String[] config;
private final File storeDir;
final int machineId;
private Bootstrap( File storeDir, int machineId, String... config )
{
this.storeDir = storeDir;
this.machineId = machineId;
this.config = config;
}
final HighlyAvailableGraphDatabase start()
{
Map<String, String> params = new HashMap<String, String>();
params.put( HighlyAvailableGraphDatabase.CONFIG_KEY_HA_MACHINE_ID,
Integer.toString( machineId ) );
for ( int i = 0; i < config.length; i += 2 )
{
params.put( config[i], config[i + 1] );
}
return start( storeDir.getAbsolutePath(), params );
}
abstract HighlyAvailableGraphDatabase start( String storeDir, Map<String, String> config );
}
private static abstract class DatabaseReference
{
abstract HighlyAvailableGraphDatabase graph() throws StartupFailureException;
void shutdown()
{
}
}
private static class HaDbProcess extends SubProcess<Controller, Bootstrap> implements
Controller
{
private transient volatile DatabaseReference db = null;
private final String testMethodName;
private HaDbProcess( String testMethodName )
{
this.testMethodName = testMethodName;
}
private HighlyAvailableGraphDatabase db() throws StartupFailureException
{
DatabaseReference ref = db;
if ( ref == null ) throw new IllegalStateException( "database has not been started" );
return ref.graph();
}
private synchronized void db( final HighlyAvailableGraphDatabase graph )
{
if ( db != null && graph != null )
{
graph.shutdown();
throw new IllegalStateException( "database has already been started" );
}
db = new DatabaseReference()
{
@Override
HighlyAvailableGraphDatabase graph()
{
if ( graph == null )
throw new IllegalStateException( "database has been shut down" );
return graph;
}
@Override
void shutdown()
{
if ( graph == null )
{
System.out.println( "database has already been shut down" );
}
else
{
graph.shutdown();
}
}
};
}
private synchronized Throwable db( final Throwable cause )
{
db = new DatabaseReference()
{
@Override
HighlyAvailableGraphDatabase graph() throws StartupFailureException
{
throw new StartupFailureException( cause );
}
};
return cause;
}
@Override
public String toString()
{
return testMethodName;
}
@Override
protected synchronized void startup( Bootstrap bootstrap ) throws Throwable
{
System.setOut( new TimestampStream( System.out ) );
System.setErr( new TimestampStream( System.err ) );
if ( db != null ) throw new IllegalStateException( "already started" );
System.out.println( "About to start" );
try
{
db( bootstrap.start() );
}
catch ( Throwable exception )
{
System.out.println( "Startup failed: " + exception );
throw db( exception );
}
}
@Override
protected synchronized void shutdown()
{
DatabaseReference ref = db;
if ( ref == null )
{
System.out.println( "Shutdown attempted before completion of startup" );
throw new IllegalStateException( "database has not been started" );
}
System.out.println( "Shutdown started" );
try
{
ref.shutdown();
}
finally
{
db( (HighlyAvailableGraphDatabase) null );
}
System.out.println( "Shutdown completed" );
super.shutdown();
}
public void awaitStarted() throws StartupFailureException
{
boolean interrupted = false;
while ( this.db == null )
{
try
{
Thread.sleep( 100 );
}
catch ( InterruptedException e )
{
interrupted = true;
Thread.interrupted();
}
}
if ( interrupted ) Thread.currentThread().interrupt();
db();
}
public <T> T executeJob( Job<T> job ) throws Exception
{
HighlyAvailableGraphDatabase database = db();
System.out.println( "Executing job " + job );
T result = job.execute( database );
System.out.println( "Job " + job + " executed" );
return result;
}
public int getMachineId() throws StartupFailureException
{
return Integer.parseInt( db().getManagementBean( HighAvailability.class ).getMachineId() );
}
public void pullUpdates() throws StartupFailureException
{
HighlyAvailableGraphDatabase database = db();
System.out.println( "pullUpdates" );
database.pullUpdates();
}
}
private static class TimestampStream extends PrintStream
{
static ThreadLocal<DateFormat> timestamp = new ThreadLocal<DateFormat>()
{
@Override
protected DateFormat initialValue()
{
return new SimpleDateFormat( "[HH:mm:ss:SS] " );
}
};
static String format( Date date )
{
return timestamp.get().format( date );
}
TimestampStream( PrintStream out )
{
super( out );
}
@Override
public void println( String string )
{
super.println( format( new Date() ) + string );
}
}
}