Search code examples
javaspringspring-bootspring-webspring-shell

how to build console command in spring boot web application using spring shell?


I have created restfull web application using spring boot web starter which works well. I am able to access it through urls.

But I have requirement to create console command which can compute and store some values at the backend. I want to be able to run console command manually or through bash script.

I could not find any documentation on how to integrate spring-shell project in spring boot web application.

Also there is no option to choose spring-shell dependency in spring boot starter https://start.spring.io/

1) Do webapp and console need to be two separate applications and I need to deploy them separately ?

2) Is it possible to deploy web app and run console commands in the same app ?

3) What is the best approach to share common code (model, services, entities, business logic) between shell and web applications ?

Can anyone please help on this ?


Solution

  • Here's 2 options:

    (1) Rest API called from the command line

    You could create a Spring @RestController, which you then call from the command line ?

    curl -X POST -i -H "Content-type: application/json" -c cookies.txt -X POST http://hostname:8080/service -d '
        {
            "field":"value",
            "field2":"value2"
        }
        '
    

    You can easily embed this in a nice shell script.

    (2) Use spring-boot-remote-shell (deprecated)

    Though it is mainly for monitoring/administration purposes, you may use the spring-boot-remote-shell for that.

    Dependencies

    You need the following dependencies to enable the remote-shell:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-remote-shell</artifactId>
    </dependency>
    <dependency>
        <groupId>org.crsh</groupId>
        <artifactId>crsh.shell.telnet</artifactId>
        <version>1.3.0-beta2</version>
    </dependency>
    

    Groovy script:

    Add the following script in src/main/resources/custom.groovy:

    package commands
    
    import org.crsh.cli.Command
    import org.crsh.cli.Usage
    import org.crsh.command.InvocationContext
    
    class custom {
    
        @Usage("Custom command")
        @Command
        def main(InvocationContext context) {
            return "Hello"
        }
    }
    

    To get a hold of a Spring bean from this groovy script (source: https://stackoverflow.com/a/24300534/641627):

    BeanFactory beanFactory = (BeanFactory) context.getAttributes().get("spring.beanfactory");
    MyController myController = beanFactory.getBean(MyController.class);
    

    Launch your SpringBootApp

    With spring-boot-remote-shell on the classpath, the Spring Boot Application listens on port 5000 (by default). You can now do this:

    $ telnet localhost 5000
    Trying ::1...
    Connected to localhost.
    Escape character is '^]'.
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::  (v1.3.5.RELEASE)
    

    Help

    You can type help to see the list of available commands:

    NAME       DESCRIPTION                                                                                                                                                                 
    autoconfig Display auto configuration report from ApplicationContext                                                                                                                   
    beans      Display beans in ApplicationContext                                                                                                                                         
    cron       manages the cron plugin                                                                                                                                                     
    custom     Custom command                                                                                                                                                              
    dashboard                                                                                                                                                                              
    egrep      search file(s) for lines that match a pattern                                                                                                                               
    endpoint   Invoke actuator endpoints                                                                                                                                                   
    env        display the term env                                                                                                                                                        
    filter     A filter for a stream of map                                                                                                                                                
    help       provides basic help                                                                                                                                                         
    java       various java language commands                                                                                                                                              
    jmx        Java Management Extensions                                                                                                                                                  
    jul        java.util.logging commands                                                                                                                                                  
    jvm        JVM informations                                                                                                                                                            
    less       opposite of more                                                                                                                                                            
    log        logging commands                                                                                                                                                            
    mail       interact with emails                                                                                                                                                        
    man        format and display the on-line manual pages                                                                                                                                 
    metrics    Display metrics provided by Spring Boot                                                                                                                                     
    shell      shell related command                                                                                                                                                       
    sleep      sleep for some time                                                                                                                                                         
    sort       Sort a map                                                                                                                                                                  
    system     vm system properties commands                                                                                                                                               
    thread     JVM thread commands 
    

    Call our custom command

    Our custom command is listed (the fourth from the top), you can call it:

    > custom
    Hello
    

    So, essentially, your crontab would do a telnet 5000 and execute custom

    (3) How to use arguments and options (to answer question in comments)

    Arguments

    To use arguments, you can take a look at the documentation:

    class date {
      @Usage("show the current time")
      @Command
      Object main(
         @Usage("the time format")
         @Option(names=["f","format"])
         String format) {
        if (format == null)
          format = "EEE MMM d HH:mm:ss z yyyy";
        def date = new Date();
        return date.format(format);
      }
    }
    
    % date -h
    % usage: date [-h | --help] [-f | --format]
    % [-h | --help]   command usage
    % [-f | --format] the time format
    
    % date -f yyyyMMdd
    

    Sub-command (or options)

    Still from their documentation:

    @Usage("JDBC connection")
    class jdbc {
    
      @Usage("connect to database with a JDBC connection string")
      @Command
      public String connect(
              @Usage("The username")
              @Option(names=["u","username"])
              String user,
              @Usage("The password")
              @Option(names=["p","password"])
              String password,
              @Usage("The extra properties")
              @Option(names=["properties"])
              Properties properties,
              @Usage("The connection string")
              @Argument
              String connectionString) {
         ...
      }
    
      @Usage("close the current connection")
      @Command
      public String close() {
         ...
      }
    }
    
    % jdbc connect jdbc:derby:memory:EmbeddedDB;create=true
    

    The last command executes:

    • the command jdbc
    • with subcommand connect
    • and the argument jdbc:derby:memory:EmbeddedDB;create=true

    A Complete example

    The following contains:

    • a constructor;
    • a command with arguments;
    • a spring managed bean;
    • a subcommand with arguments.

    The code:

    package commands
    
    import org.crsh.cli.Command
    import org.crsh.cli.Usage
    import org.crsh.command.InvocationContext
    import org.springframework.beans.factory.BeanFactory
    import com.alexbt.goodies.MyBean
    
    class SayMessage {
        String message;
        SayMessage(){
            this.message = "Hello";
        }
    
        @Usage("Default command")
        @Command
        def main(InvocationContext context, @Usage("A Parameter") @Option(names=["p","param"]) String param) {
            BeanFactory beanFactory = (BeanFactory) context.getAttributes().get("spring.beanfactory");
            MyBean bean = beanFactory.getBean(MyBean.class);
            return message + " " + bean.getValue() + " " + param;
        }
    
        @Usage("Hi subcommand")
        @Command
        def hi(InvocationContext context, @Usage("A Parameter") @Option(names=["p","param"]) String param) {
            BeanFactory beanFactory = (BeanFactory) context.getAttributes().get("spring.beanfactory");
            MyBean bean = beanFactory.getBean(MyBean.class);
            return "Hi " + bean.getValue() + " " + param;
        }
    }
    
    > saymsg -p Johnny
    > Hello my friend Johnny
    
    > saymsg hi -p Johnny
    > Hi my friend Johnny