package org.neo4j.remote; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException; import java.util.List; public final class Agent { @SuppressWarnings( "serial" ) private static class StartupException extends Exception { StartupException( String message ) { super( message ); } } private Agent( boolean attached ) throws StartupException, URISyntaxException { if ( attached ) { // TODO: implement this jarfile = null; listMethod = null; attachMethod = null; simpleAttachMethod = null; loadMethod = null; throw new StartupException( "TODO: Implement this!" ); } else { jarfile = new File( getClass().getProtectionDomain().getCodeSource().getLocation().toURI() ).getAbsolutePath(); try { Class<?> vmClass = Class.forName( "com.sun.tools.attach.VirtualMachine" ); listMethod = vmClass.getMethod( "list" ); attachMethod = vmClass.getMethod( "attach", Class.forName( "com.sun.tools.attach.VirtualMachineDescriptor" ) ); simpleAttachMethod = vmClass.getMethod( "attach", String.class ); loadMethod = vmClass.getMethod( "loadAgent", String.class, String.class ); } catch ( Exception e ) { throw new StartupException( "Remote Graph Database Agent requires Java 6" ); } } } private static class Arguments { String pid = null; boolean read_only = false; String path = null; String uri = null; // TODO: parse this! // FIXME: state machine is broken: optional positional pid then more... enum ParseState { GENERAL { @Override ParseState parse( String arg, Arguments args ) { if ( "-pid".equalsIgnoreCase( arg ) ) { return PID; } else if ( "-read_only".equalsIgnoreCase( arg ) ) { args.read_only = true; return this; } else { args.uri = arg; if ( args.pid == null ) { return PID_SHIFT; } else { return PATH; } } } }, PID { @Override ParseState parse( String arg, Arguments args ) { args.pid = arg; return GENERAL; } }, PID_SHIFT { @Override ParseState parse( String arg, Arguments args ) { args.pid = args.uri; args.uri = arg; return PATH; } }, PATH { @Override ParseState parse( String arg, Arguments args ) { args.pid = args.uri; args.uri = arg; return DONE; } }, DONE { @Override ParseState parse( String arg, Arguments args ) throws StartupException { throw new StartupException( "Too many arguments." ); } }; abstract ParseState parse( String arg, Arguments args ) throws StartupException; } Arguments( String[] args ) throws StartupException { ParseState state = ParseState.GENERAL; for ( String arg : args ) { state = state.parse( arg, this ); } if ( path == null ) { throw new StartupException( "No path specified!" ); } if ( uri == null ) { throw new StartupException( "No resource uri specified!" ); } } Arguments( String agentArgs ) { int pos = 0; while ( pos < agentArgs.length() ) { int start = pos; pos = agentArgs.indexOf( ':', pos ); check( pos ); String key = agentArgs.substring( start, pos ); start = ( pos-- ) + 1; do { pos += 2; pos = agentArgs.indexOf( ';', pos ); check( pos ); if ( pos + 1 == agentArgs.length() ) break; } while ( agentArgs.charAt( pos + 1 ) == ';' ); String value = agentArgs.substring( start, pos ); pos++; if ( key.equalsIgnoreCase( "read_only" ) ) { read_only = Boolean.parseBoolean( value ); } else if ( key.equalsIgnoreCase( "path" ) ) { path = value.replace( ";;", ";" ); } else if ( key.equalsIgnoreCase( "uri" ) ) { uri = value.replace( ";;", ";" ); } } } private static void check( int pos ) { if ( pos == -1 ) { throw new IllegalArgumentException( "Illegally formatted string representation of Arguments." ); } } @Override public boolean equals( Object obj ) { if ( obj instanceof Arguments ) { Arguments other = (Arguments) obj; if ( other.read_only == read_only ) if ( path == other.path || path.equals( other.path ) ) if ( uri == other.uri || uri.equals( other.uri ) ) return true; } return false; } @Override public String toString() { StringBuilder result = new StringBuilder( "read_only:" ); result.append( read_only ); result.append( ";" ); if ( path != null ) { result.append( "path:" ); result.append( path.replace( ";", ";;" ) ); result.append( ";" ); } if ( uri != null ) { result.append( "uri:" ); result.append( uri.replace( ";", ";;" ) ); result.append( ";" ); } return result.toString(); } } private static volatile boolean agent_vm = false; private final String jarfile; private final Method listMethod; private final Method attachMethod; private final Method simpleAttachMethod; private final Method loadMethod; public static void main( String[] args ) throws URISyntaxException { final Arguments arguments; final Agent agent; try { arguments = new Arguments( args ); agent = new Agent( false ); } catch ( StartupException err ) { System.err.println( err.getMessage() ); System.exit( 1 ); return; } // <TEST> if ( !arguments.equals( new Arguments( arguments.toString() ) ) ) { System.err.println( "Implementation error in serialization!" ); System.exit( 1 ); return; } // </TEST> agent_vm = true; try { agent.attach( arguments ); } catch ( Exception ex ) { System.err.println( "Attachment process failed with an unexpected exception." ); ex.printStackTrace(); } finally { agent_vm = false; } } public static void agentmain( String agentArgs ) { if ( !agent_vm ) { final Agent agent; try { agent = new Agent( true ); } catch ( StartupException exception ) { return; // Neo4j is not loaded in this JVM } catch ( URISyntaxException ex ) // Should never happen { throw new RuntimeException( ex ); } try { agent.dispatch( new Arguments( agentArgs ) ); } catch ( Exception ex ) { final Throwable exception; if ( ex instanceof InvocationTargetException ) { exception = ( (InvocationTargetException) ex ).getTargetException(); } else { exception = ex; } System.err.println( "Failed to attach Remote Graph Database Agent." ); exception.printStackTrace(); } } } private void attach( Arguments arguments ) throws Exception { if ( arguments.pid != null ) { attach( simpleAttachMethod, arguments.pid, arguments ); } else { for ( Object descriptor : (List<?>) listMethod.invoke( null ) ) { attach( attachMethod, descriptor, arguments ); } } } private void attach( Method attach, Object id, Arguments arguments ) throws Exception { try { Object vm = attach.invoke( null, id ); loadMethod.invoke( vm, jarfile, arguments.toString() ); } catch ( InvocationTargetException ex ) { System.err.println( "Could not attach to " + id ); } } private void dispatch( Arguments arguments ) throws Exception { /* TODO: implement this. * o We need some way to access all loaded EmbeddedGraphDBs * o We need to be able to match the path of the graphdb * with the supplied path. */ } }