The core components of the shell are its plugin model, built-in commands, and converters
The plugin model is based Spring. Each plugin jar is required to
contain the file
META-INF/spring/spring-shell-plugin.xml
. These files
will be loaded to bootstrap a Spring
ApplicationContext
when the shell is
started. The essential boostrapping code that looks for your contributions
looks like this
new ClassPathXmlApplicationContext("classpath*:/META-INF/spring/spring-shell-plugin.xml");
In the spring-shell-plugin.xml
file you should
define the command classes and any other collaborating objects that
support the command's actions. The plugin model is depicted in the
following diagram
Note that the current plugin model loads all plugins under the same class loader. An open JIRA issus is to provide a classloader per plugin to provide isolation.
An easy way to declare the commands is to use Spring's component
scanning functionality. Here is an example
spring-shell-plugin.xml
that from the sample
application.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <context:component-scan base-package="org.springframework.shell.samples.helloworld.commands" /> </beans>
The commands are Spring components, demarcated as such using the
@Component
annotation. For example, the shell of the
HelloWorldCommands
class from the sample
application looks like this
@Component public class HelloWorldCommands implements CommandMarker { // use any Spring annotations for Dependency Injection or other Spring interfaces as required. // methods with @Cli annotations go here }
One the commands are registered and instantiated by the Spring
container, they are registered with the core shell parser so that the
@Cli
annotations can be processed. The way the
commands are identified is through the use of the
CommandMarker
interface.
The
org.springframework.shell.core.Converter
interface provides the contract to convert the strings that are entered
on the command line to rich Java types passed into the arguments of
@Cli
-annotated methods.
By default converters for common types are registered. These cover primitive types (boolean, int, float...) as well as Date, Character, and File.
If you need to register any additional
Converter
instances, register them with
the Spring container in the
spring-shell-plugin.xml
file and they will be
picked up automatically.
There are a few built in commands. Here is a listing of their class name and functionality
EssentialCommands
-
exit
and quit
- to exit the
shell.
HelpCommands
- help
-
list all commands and their usage
OsCommands
- the keyword for this command
is the exclamation point, !
. After the exclamation
point you can pass in a unix/windows command string to be
executed.
There are also a few commands that are provided by the
AbstractShell
class, these are
date
- Displays the local date and
time
script
- Parses the specified resource file
and executes its commands
system properties
- Shows the shell's
properties
version
- Displays current CLI version
There are a few extension points that allow you to customize the shell. The extension points are the interfaces
BannerProvider
- Specifies the
banner text, welcome message, and version number that will be
displayed when the shell is started
PromptProvider
- Specifies the
command prompt text, eg. "shell>
" or
"#
" or "$
"
HistoryFileNameProvider
-
Specifies the name of the command history file
There is a default implementation for these interfaces but you
should create your own implementations for your own shell application. Use
Spring's @Ordered
annotation to set the priority of the
provider. This allows your provider implementations to take precidence
over any other implementations that maybe present on the classpath from
other plugins.
To make cool "ASCII art" banners the website http://patorjk.com/software/taag is quite neat!
As this is a standard Spring application you can use Spring's ApplicationContext event infrastructure to communicate across plugins.
It has shown to be useful to provide a simple form of interception
around the invocation of a command method. This enables the command class
to check for updates to state, such as configuration information modified
by other plugins, before the command method is executed. The interface
ExecutionProcess
should be implemented
instead of CommandMarker
to access this
functionality. The ExecutionProcess
interface is shown below
public interface ExecutionProcessor extends CommandMarker { /** * Method called before invoking the target command (described by {@link ParseResult}). * Additionally, for advanced cases, the parse result itself effectively changing the invocation * calling site. * * @param invocationContext target command context * @return the invocation target */ ParseResult beforeInvocation(ParseResult invocationContext); /** * Method called after successfully invoking the target command (described by {@link ParseResult}). * * @param invocationContext target command context * @param result the invocation result */ void afterReturningInvocation(ParseResult invocationContext, Object result); /** * Method called after invoking the target command (described by {@link ParseResult}) had thrown an exception . * * @param invocationContext target command context * @param thrown the thrown object */ void afterThrowingInvocation(ParseResult invocationContext, Throwable thrown); }
There are a few command line options that can be specified when starting the shell. They are
--profiles
- Specifies values for the system
property spring.profiles.active so that Spring 3.1 and greater profile
support is enabled.
--cmdfile
- Specifies a file to read that
contains shell commands
--histsize
- Specifies the maximum number of
lines to store in the command history file. Default value is
3000.
Scripts can be executed either by passing in the
--cmdfile
argument at startup or by executing the
script
command inside the shell. When using scripts it
helps to add comments and this can be done using block comments that start
and end with /*
and */
or an inline
one line command using //
or ;
characters.