Jane Wayne
Jane Wayne

Reputation: 8865

Groovy command using inheritance cannot compile in Spring Boot remote shell

I have an abstract groovy class with some utility methods that I want to extend from other groovy command classes (for use in Spring Boot's remote shell). However, when I attempt to run the groovy command class, I get a CommandException.

My groovy abstract class looks like the following.

package commands 
import com.xyz.MyService
import org.crsh.command.InvocationContext
import org.springframework.beans.factory.BeanFactory

abstract class abstractcmd {
    private static final String SPRING_FACTORY = "spring.beanfactory"

    protected MyService getMyService(InvocationContext context) {
        return getBeanFactory(context).getBean(MyService.class);
    }

    private BeanFactory getBeanFactory(InvocationContext context) {
        return context.attributes[SPRING_FACTORY];
    }
}

My groovy command class looks like the following.

package commands
import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

@Usage("do something commands")
class foo extends abstractcmd {
    @Command
    @Usage("bar")
    def String bar(InvocationContext context) {
        try {
            getMyService(context).bar()
            return "did bar"
        } catch (Exception e) {
            return String.format("could not do bar: %s", e.toString())
        }
    }
}

When I SSH into the shell and execute foo bar I get the following exception.

org.crsh.shell.impl.command.spi.CommandException: Could not create command foo instance
    at org.crsh.lang.impl.groovy.GroovyCompiler$1.getCommand(GroovyCompiler.java:192) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.LanguageCommandResolver.resolveCommand(LanguageCommandResolver.java:101) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSH.getCommand(CRaSH.java:100) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.getCommand(CRaSHSession.java:96) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.PipeLineFactory.create(PipeLineFactory.java:89) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.ScriptRepl.eval(ScriptRepl.java:88) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.createProcess(CRaSHSession.java:163) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.async.AsyncProcess.execute(AsyncProcess.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.iterate(Console.java:219) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:158) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:135) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.jline.JLineProcessor.run(JLineProcessor.java:204) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.ssh.term.CRaSHCommand.run(CRaSHCommand.java:99) ~[crash.connectors.ssh-1.3.2.jar:?]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

If I remove the package commands line from both the abstractcmd and foo classes, then the IDE (IntelliJ) and shell both complain about abstractcmd not being found.

2017-03-19 19:34:06 ERROR org.crsh.shell.impl.command.CRaSHProcess.execute:84 - Error while evaluating request 'foo' Could not compile command script foo
org.crsh.shell.impl.command.spi.CommandException: Could not compile command script foo
    at org.crsh.lang.impl.groovy.GroovyClassFactory.parse(GroovyClassFactory.java:65) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.groovy.GroovyCompiler$1.getCommand(GroovyCompiler.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.LanguageCommandResolver.resolveCommand(LanguageCommandResolver.java:101) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSH.getCommand(CRaSH.java:100) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.getCommand(CRaSHSession.java:96) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.PipeLineFactory.create(PipeLineFactory.java:89) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.ScriptRepl.eval(ScriptRepl.java:88) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.createProcess(CRaSHSession.java:163) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.async.AsyncProcess.execute(AsyncProcess.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.iterate(Console.java:219) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:158) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:135) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.jline.JLineProcessor.run(JLineProcessor.java:204) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.ssh.term.CRaSHCommand.run(CRaSHCommand.java:99) ~[crash.connectors.ssh-1.3.2.jar:?]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]
Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
foo: 9: unable to resolve class abstractcmd 
 @ line 9, column 1.
   @Usage("bar")
   ^

1 error

    at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:946) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:593) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:542) ~[groovy-2.4.7.jar:2.4.7]
    at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298) ~[groovy-2.4.7.jar:2.4.7]
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268) ~[groovy-2.4.7.jar:2.4.7]
    at org.crsh.lang.impl.groovy.GroovyClassFactory.parse(GroovyClassFactory.java:59) ~[crash.shell-1.3.2.jar:?]
    ... 14 more

The only way that I am able to get a custom command to work properly if is I don't extend abstractcmd (thereby implement the "util" methods in the command class) and remove the package line.

I am using Spring Boot v1.5.1.RELEASE.

Any ideas on what I'm doing wrong?

Upvotes: 0

Views: 325

Answers (1)

Morfic
Morfic

Reputation: 15518

Looks like CRaSH has issues with groovy classes extending other groovy classes, because if you don't extend one, it works just fine.

I'm sorry but I did not have time to fully investigate how and why (corrections/updates are more than welcome), and didn't find anything in the docs but the good news is that you can extend an abstract java class to achieve the same thing. I stumbled upon such a sample for the cron command that extends a GroovyCommand abstract class with the following javadoc:

/**
 * The base command for Groovy class based commands.
 */
public abstract class GroovyCommand extends BaseCommand implements GroovyObject

So, to cut a long story short, you could use the following workaround:

Abstract SpringAwareCommand java class

package crash.commands;

import org.crsh.command.BaseCommand;
import org.springframework.beans.factory.BeanFactory;

public abstract class SpringAwareCommand extends BaseCommand {

    private static final String SPRING_BEAN_FACTORY = "spring.beanfactory";

    protected <T> T getBean(Class<T> beanClass) {
        return ((BeanFactory) this.context.getAttributes().get(SPRING_BEAN_FACTORY)).getBean(beanClass);
    }

}

Updated foo groovy class

package crash.commands

import com.xyz.MyService
import org.crsh.cli.Command
import org.crsh.cli.Usage

@Usage("do something commands")
class foo extends SpringAwareCommand {
    @Command
    @Usage("bar")
    def String bar() {
        try {
            getBean(MyService.class).bar()
            return "did bar"
        } catch (Exception e) {
            return String.format("could not do bar: %s", e.toString())
        }
    }
}

Upvotes: 1

Related Questions