package org.neo4j.util.shell.apps.extra; import java.lang.reflect.Method; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.neo4j.util.shell.AbstractApp; import org.neo4j.util.shell.CommandParser; import org.neo4j.util.shell.Output; import org.neo4j.util.shell.Session; import org.neo4j.util.shell.ShellException; /** * A way to execute groovy scripts from the shell. It doesn't use the groovy * classes directly, but instead purely via reflections... This gives the * advantage of not being dependent on groovy at compile-time. * * So if the groovy classes is in the classpath at run-time then groovy scripts * can be executed, otherwise it will say that "Groovy isn't available". * * It has the old style script/argument format of: * sh$ gsh --script1 arg1 arg2 arg3 --script2 arg1 arg2 * * The paths to look for groovy scripts is decided by the environment variable * GSH_PATH, also there are some default paths: ".", "script", "src/script". */ public class Gsh extends AbstractApp { public static final String BINDING_CLASS = "groovy.lang.Binding"; public static final String ENGINE_CLASS = "groovy.util.GroovyScriptEngine"; public static final String PATH_STRING = "GSH_PATH"; public String execute( CommandParser parser, Session session, Output out ) throws ShellException { this.ensureGroovyIsInClasspath(); if ( parser.getLineWithoutCommand().trim().length() == 0 ) { throw new ShellException( "Need to supply groovy scripts" ); } List<String> pathList = this.getEnvPaths( session ); Object groovyScriptEngine = this.newGroovyScriptEngine( pathList.toArray( new String[ pathList.size() ] ) ); Map<String, Object> properties = new HashMap<String, Object>(); properties.put( "out", out ); properties.put( "session", session ); this.runGroovyScripts( groovyScriptEngine, properties, parser ); return null; } private void runGroovyScripts( Object groovyScriptEngine, Map<String, Object> properties, CommandParser parser ) throws ShellException { ArgReader reader = new ArgReader( CommandParser.tokenizeStringWithQuotes( parser.getLineWithoutCommand() ) ); HashMap<String, Object> hashMap = ( HashMap<String, Object> ) properties; while ( reader.hasNext() ) { String arg = reader.next(); if ( arg.startsWith( "--" ) ) { String[] scriptArgs = getScriptArgs( reader ); String scriptName = arg.substring( 2 ); Map<String, Object> props = ( Map<String, Object> ) hashMap.clone(); props.put( "args", scriptArgs ); this.runGroovyScript( groovyScriptEngine, scriptName, this.newGroovyBinding( props ) ); } } } private void runGroovyScript( Object groovyScriptEngine, String scriptName, Object groovyBinding ) throws ShellException { try { Method runMethod = groovyScriptEngine.getClass().getMethod( "run", String.class, groovyBinding.getClass() ); runMethod.invoke( groovyScriptEngine, scriptName + ".groovy", groovyBinding ); } catch ( Exception e ) { throw new ShellException( "Groovy exception: " + this.findProperMessage( e ), e ); } } private String findProperMessage( Throwable e ) { String message = e.getMessage(); if ( ( message == null || message.length() == 0 ) && e.getCause() != null ) { message = this.findProperMessage( e.getCause() ); } return message; } private String[] getScriptArgs( ArgReader reader ) { reader.mark(); try { ArrayList<String> list = new ArrayList<String>(); while ( reader.hasNext() ) { String arg = reader.next(); if ( arg.startsWith( "--" ) ) { break; } list.add( arg ); reader.mark(); } return list.toArray( new String[ list.size() ] ); } finally { reader.flip(); } } private List<String> getEnvPaths( Session session ) throws ShellException { try { String env = ( String ) session.get( PATH_STRING ); List<String> list = new ArrayList<String>(); if ( env != null && env.trim().length() > 0 ) { for ( String path : env.split( ":" ) ) { list.add( path ); } } // Some default paths list.add( "." ); list.add( "script" ); list.add( "src/script" ); return list; } catch ( RemoteException e ) { throw new ShellException( e ); } } private Object newGroovyBinding( Map<String, Object> properties ) throws ShellException { try { Class cls = Class.forName( BINDING_CLASS ); Object binding = cls.newInstance(); Method setPropertyMethod = cls.getMethod( "setProperty", String.class, Object.class ); for ( String key : properties.keySet() ) { setPropertyMethod.invoke( binding, key, properties.get( key ) ); } return binding; } catch ( Exception e ) { throw new ShellException( "Invalid groovy classes", e ); } } private Object newGroovyScriptEngine( String[] paths ) throws ShellException { try { Class cls = Class.forName( ENGINE_CLASS ); return cls.getConstructor( String[].class ).newInstance( new Object[] { paths } ); } catch ( Exception e ) { throw new ShellException( "Invalid groovy classes", e ); } } private void ensureGroovyIsInClasspath() throws ShellException { try { Class.forName( BINDING_CLASS ); } catch ( ClassNotFoundException e ) { throw new ShellException( "Groovy couldn't be found", e ); } } private static class ArgReader implements Iterator<String> { private static final int START_INDEX = -1; private int index = START_INDEX; private String[] args; private Integer mark; ArgReader( String[] args ) { this.args = args; } public boolean hasNext() { return this.index + 1 < this.args.length; } public String next() { if ( !hasNext() ) { throw new NoSuchElementException(); } this.index++; return this.args[ this.index ]; } public void previous() { this.index--; if ( this.index < START_INDEX ) { this.index = START_INDEX; } } public void remove() { throw new UnsupportedOperationException(); } public void mark() { this.mark = this.index; } public void flip() { if ( this.mark == null ) { throw new IllegalStateException(); } this.index = this.mark; this.mark = null; } } @Override public String getDescription() { return "Runs groovy scripts. Usage: gsh <groovy script line>"; } }