diff --git a/docs/src/reference/docbook/images/shell-example-enum.jpg b/docs/src/reference/docbook/images/shell-example-enum.jpg new file mode 100644 index 00000000..a12af2d4 Binary files /dev/null and b/docs/src/reference/docbook/images/shell-example-enum.jpg differ diff --git a/docs/src/reference/docbook/images/shell-example.jpg b/docs/src/reference/docbook/images/shell-example.jpg new file mode 100644 index 00000000..4148897c Binary files /dev/null and b/docs/src/reference/docbook/images/shell-example.jpg differ diff --git a/docs/src/reference/docbook/index.xml b/docs/src/reference/docbook/index.xml index ce8cb3b7..ae9352ba 100644 --- a/docs/src/reference/docbook/index.xml +++ b/docs/src/reference/docbook/index.xml @@ -16,16 +16,16 @@ Pollack SpringSource - - Jarred - Li - VMware - Costin Leau SpringSource + + Jarred + Li + VMware + @@ -63,7 +63,7 @@ Spring Shell Sample application - + \ No newline at end of file diff --git a/docs/src/reference/docbook/introduction/introduction.xml b/docs/src/reference/docbook/introduction/introduction.xml index 212f77ab..f794e8aa 100644 --- a/docs/src/reference/docbook/introduction/introduction.xml +++ b/docs/src/reference/docbook/introduction/introduction.xml @@ -6,7 +6,7 @@ xmlns:ns2="http://www.w3.org/1999/xlink" xmlns:ns="http://docbook.org/ns/docbook"> The Spring Shell provides an interactive shell that lets you - contribute commands using a simple POJO based programming model. + contribute commands using a simple Spring based programming model. This document is the reference guide for the Spring Shell and covers the key classes that are part of the Shell infrastructure, the plugin model, diff --git a/docs/src/reference/docbook/introduction/requirements.xml b/docs/src/reference/docbook/introduction/requirements.xml index 19dde886..f3175eef 100644 --- a/docs/src/reference/docbook/introduction/requirements.xml +++ b/docs/src/reference/docbook/introduction/requirements.xml @@ -8,11 +8,7 @@ xmlns:ns="http://docbook.org/ns/docbook"> Requirements - The Spring Shell requires JDK level 6.0 (just like Hadoop) and above - and the Spring Framework 3.0 (3.1 - recommended) and above. + The Spring Shell requires JDK level 6.0 and above as well as the + Spring Framework 3.0 + (3.1 recommended) and above. diff --git a/docs/src/reference/docbook/preface.xml b/docs/src/reference/docbook/preface.xml index 74418e05..a6bc3f99 100644 --- a/docs/src/reference/docbook/preface.xml +++ b/docs/src/reference/docbook/preface.xml @@ -8,45 +8,40 @@ Preface The Spring Shell provides an interactive shell that allows you to - plugin your own custom commands using a Spring based POJO programming + plugin your own custom commands using a Spring based programming model. - The shell has been extracted from the Spring Roo project, giving it a - strong foundation and rich feature set. One significant change from Spring - Roo is that the plugin model is no longer based on OSGi but instead uses - Spring IoC container to discover commands through classpath scanning. There - is currently no classloader isolation between plugins, however that maybe - added in future versions. + The shell has been extracted from the Spring Roo + project, giving it a strong foundation and rich feature set. One + significant change from Spring Roo is that the plugin model is no longer + based on OSGi but instead uses Spring IoC container to discover commands + through classpath scanning. There is currently no classloader isolation + between plugins, however that maybe added in future versions. Spring Shell's features include - A POJO based programming model to contribute custom - commands + A simple, annotation driven, programming model to contribute + custom commands - Use Spring's classpath scanning functionality as a basis for a - command plugin strategy + Use of Spring's classpath scanning functionality asthe basis for a + command plugin strategy and command develoment Inheritance of the Roo - Shell features + Shell features, most notably tab completion, colorization, and + script execution. - Tab completion - - - - Scripting and Script recording - - - - Customize command prompt, banner, shell history file name. + Customizatin of command prompt, banner, shell history file + name. diff --git a/docs/src/reference/docbook/reference/dev-guide/dev-spring-shell.xml b/docs/src/reference/docbook/reference/dev-guide/dev-spring-shell.xml index 1ac18fde..35554e18 100644 --- a/docs/src/reference/docbook/reference/dev-guide/dev-spring-shell.xml +++ b/docs/src/reference/docbook/reference/dev-guide/dev-spring-shell.xml @@ -10,35 +10,151 @@ Developing Spring Shell Applications Contributing commands to the shell is very easy. There are only a few - annotations you need to learn. The implementation style of the command is in - the style of developing an application that uses dependency injection as you - can leverage all the features of the Spring container. + annotations you need to learn. The implementation style of the command is + the same as developing classes in for application that uses dependency + injection. You can leverage all the features of the Spring container to + implement your command classes.
Marker Interface - The first step to creating a command is to implement the market - interface CommandMarker and to annotate your class with Spring's - @Component annotation. (Note there is an open JIRA issue to provide a - @CliCommand meta-annotation to avoid having to use a market interface) - Taking the -
+ The first step to creating a command is to implement the marker + interface CommandMarker and to annotate + your class with Spring's @Component annotation. + (Note there is an open JIRA issue to provide a + @CliCommand meta-annotation to avoid having to use + a marker interface). Using the code from the helloworld sample + application, the shell of a HelloWorldCommands + class is shown below -
- CLI Annotations + @Component +public class HelloWorldCommands implements CommandMarker { + + // use any Spring annotations for Dependency Injection or other Spring interfaces as required. - annotations + // methods with @Cli annotations go here + +}
Logging - + Logging is done using JDK logging. Simply add a LOG declaration as + shown below to use a logger. + + @Component +public class HelloWorldCommands implements CommandMarker { + + protected final Logger LOG = Logger.getLogger(getClass().getName()); + + // methods with @Cli annotations go here + +} +
+ +
+ CLI Annotations + + There are three annotations used on methods and method arguments + that define main contract for interacting with the shell. These are + + + + CliAvailabilityIndicator - Placed on a + method that returns a boolean value and indicates if a particular + command can be presented in the shell. This decision is usually based + on the history of commands that have been executed previously. It + prevents extraneous commands being presented until some preconditions + are met, for example the execution of a 'configuration' + command. + + + + CliCommand - Placed on a method that + provides a command to the shell. Its value provides one or more + strings that serve as the start of a particular command name. These + must be unique within the entire application, across all + plugins. + + + + CliOptions - Placed on the arguments of a + command methods, allowing it to declare the argument value as + mandatory or optional with a default value. + + + + Here is a simple use of these annotations in a command class + + @Component +public class HelloWorldCommands implements CommandMarker { + + protected final Logger LOG = Logger.getLogger(getClass().getName()); + + @CliAvailabilityIndicator({"hw simple"}) + public boolean isCommandAvailable() { + return true; + } + + @CliCommand(value = "hw simple", help = "Print a simple hello world message") + public void simple( + @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message, + @CliOption(key = { "location" }, mandatory = false, help = "Where you are saying hello", specifiedDefaultValue="At work") final String location) { + + LOG.info("Message = [" + message + "] Location = [" + location + "]"); + + } +} + + The method annotated with @CliAvailabilityIndicator + is returning true so that the one and only command in this + class is exposed to the shell to be invoked. If there were more commands + in the class, you would list them as comma separated value. + + The @CliCommand annotation is creating the + command 'hw simple' in the shell. The help message is + what will be printed if you use the build in help + command. The method name is 'simple' but it could + just have well been any other name. + + The @CliOption annotation on each of the + command arguments is where you will spend most of your time authoring + commands. You need to decide which arguments are required, which are + optional, and if they are optional is there a default value. In this case + there are two arguments or keys to the command, message and location. The + key message is required and a help message is provided to give guidance to + the user when tabbing to get completion for the command. + + The implementation of the 'simple' method is trivial, just a log + statement, but this is where you would typically call other collaborating + objects that were injected into the class via Spring. + + The method argument types in this example are + String, which doesn't present any issue with type + conversion. You can specify methods with any rich object type including + basic primitive types such as int, float etc. For all types other than + those handled by the shell by default (basic types, + Date, File) you will need to + register your own implementation of the + org.springframework.shell.core.Converter + interface with the container in your plugin.
Building and running the shell - + In our opinion, the easiest way to build an execute the shell is to + cut-n-paste the gradle script in the example application. This uses the + application plugin from gradle to create a bin directory with a startup + script for windows and Unix and places all dependent jars in a lib + directory. Maven has a similar plugin - the AppAssembler + plugin. + + The main class of the shell is + org.springframework.shell.Bootstrap. As long as you + place other plugins, perhaps developed independently, on the classpath, + the Bootstrap class will incorporate them into the shell.
diff --git a/docs/src/reference/docbook/reference/shell.xml b/docs/src/reference/docbook/reference/shell.xml index bbae8319..e8a8c9c0 100644 --- a/docs/src/reference/docbook/reference/shell.xml +++ b/docs/src/reference/docbook/reference/shell.xml @@ -23,8 +23,8 @@ looks like thisnew 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 collaboration objects that - support the commands actions. The plugin model is depicted in the + define the command classes and any other collaborating objects that + support the command's actions. The plugin model is depicted in the following diagram @@ -41,7 +41,7 @@ Commands An easy way to declare the commands is to use Spring's component - scanning functionality.Here is an example + scanning functionality. Here is an example spring-shell-plugin.xml that from the sample application. @@ -56,7 +56,7 @@ </beans> The commands are Spring components, demarcated as such using the - @Component annotation. For example, the + @Component annotation. For example, the shell of the HelloWorldCommands class from the sample application looks like this @@ -82,7 +82,7 @@ public class HelloWorldCommands implements CommandMarker { The org.springframework.shell.core.Converter interface provides the contract to convert the strings that are entered - in the command to rich Java types passed into the arguments of + 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 @@ -122,6 +122,30 @@ public class HelloWorldCommands implements CommandMarker { 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 + +
@@ -133,13 +157,14 @@ public class HelloWorldCommands implements CommandMarker { BannerProvider - Specifies the - banner text , welcome message, and version number that will be + banner text, welcome message, and version number that will be displayed when the shell is started PromptProvider - Specifies the - command prompt text + command prompt text, eg. "shell>" or + "#" or "$" @@ -176,10 +201,10 @@ public class HelloWorldCommands implements CommandMarker { 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 is executed. The interface + by other plugins, before the command method is executed. The interface ExecutionProcess should be implemented instead of CommandMarker to access this - functionlatiy. The ExecutionProcess + functionality. The ExecutionProcess interface is shown below public interface ExecutionProcessor extends CommandMarker { @@ -212,4 +237,43 @@ public class HelloWorldCommands implements CommandMarker { }
+ +
+ Command line options + + 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 and comments + + 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. +
diff --git a/docs/src/reference/docbook/samples/simple-application.xml b/docs/src/reference/docbook/samples/simple-application.xml index cad272a1..9d5f4544 100644 --- a/docs/src/reference/docbook/samples/simple-application.xml +++ b/docs/src/reference/docbook/samples/simple-application.xml @@ -1,5 +1,6 @@ - Simple sample application using the Spring Shell -
+
Introduction - This sample demonstrates how to execute a MapReduce application and a script that interacts with HDFS inside a Spring based application. It does not use spring Batch or Spring Integration. + The sample application named 'helloworld' contains three + 'hw' commands, they are 'hw simple', + 'hw complex' and 'hw enum' and + demonstrate simple to intermediate level usage of the + @Cli annotation classes for creating commands. The example code is located in the distribution directory - <spring-hadoop-install-dir>/samples/wordcount. + <spring-shell-install-dir>/samples/helloworld. + + To build the example cd to the helloworld directory and execute + ..\..\gradlew installApp. To run the application cd to + build\install\helloworld\bin and execute the helloworld + script. +
+ +
+ HelloWorldCommands + + The HelloWorldCommands class is show + below + + package org.springframework.shell.samples.helloworld.commands; + + +import java.util.logging.Logger; + +import org.springframework.shell.core.CommandMarker; +import org.springframework.shell.core.annotation.CliAvailabilityIndicator; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; +import org.springframework.stereotype.Component; + +@Component +public class HelloWorldCommands implements CommandMarker { + + protected final Logger LOG = Logger.getLogger(getClass().getName()); + + private boolean simpleCommandExecuted = false; + + @CliAvailabilityIndicator({"hw simple"}) + public boolean isSimpleAvailable() { + //always available + return true; + } + + @CliAvailabilityIndicator({"hw complex", "hw enum"}) + public boolean isComplexAvailable() { + if (simpleCommandExecuted) { + return true; + } else { + return false; + } + } + + @CliCommand(value = "hw simple", help = "Print a simple hello world message") + public void simple( + @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message, + @CliOption(key = { "location" }, mandatory = false, help = "Where you are saying hello", specifiedDefaultValue="At work") final String location) { + LOG.info("Message = [" + message + "] Location = [" + location + "]"); + simpleCommandExecuted = true; + } + + @CliCommand(value = "hw complex", help = "Print a complex hello world message") + public void hello( + @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message, + @CliOption(key = { "name1"}, mandatory = true, help = "Say hello to the first name") final String name1, + @CliOption(key = { "name2" }, mandatory = true, help = "Say hello to a second name") final String name2, + @CliOption(key = { "time" }, mandatory = false, specifiedDefaultValue="now", help = "When you are saying hello") final String time, + @CliOption(key = { "location" }, mandatory = false, help = "Where you are saying hello") final String location) { + LOG.info("Hello " + name1 + " and " + name2 + ". Your special message is " + message + ". time=[" + time + "] location=[" + location + "]"); + } + + @CliCommand(value = "hw enum", help = "Print a simple hello world message from an enumerated value") + public void eenum( + @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final MessageType message){ + LOG.info("Hello. You special enumerated message is " + message); + } + + enum MessageType { + Type1("type1"), + Type2("type2"), + Type3("type3"); + + private String type; + + private MessageType(String type){ + this.type = type; + } + + public String getType(){ + return type; + } + } +} + + + The use of the @CliAvailabilityIndicator + annotation on two methods, isSimpleAvailable and + isComplexAvailable shows how you can enable the + presence of the 'hw complex' and 'hw + enum' commands only if the 'hw simple' + command was executed. + + Here is an example session showing the behavior. + + + + + + + + The 'hw enum' command shows how the shell + supports the use of Enumeration as command method arguments. + + + + + +
diff --git a/samples/helloworld/.gitignore b/samples/helloworld/.gitignore index 9a55f4ea..5d1172ac 100644 --- a/samples/helloworld/.gitignore +++ b/samples/helloworld/.gitignore @@ -5,4 +5,4 @@ target/ /log.roo *.log - +/bin/ diff --git a/samples/helloworld/src/main/java/org/springframework/shell/samples/helloworld/commands/HelloWorldCommands.java b/samples/helloworld/src/main/java/org/springframework/shell/samples/helloworld/commands/HelloWorldCommands.java index 8f1babcd..490ab14a 100644 --- a/samples/helloworld/src/main/java/org/springframework/shell/samples/helloworld/commands/HelloWorldCommands.java +++ b/samples/helloworld/src/main/java/org/springframework/shell/samples/helloworld/commands/HelloWorldCommands.java @@ -11,15 +11,34 @@ import org.springframework.stereotype.Component; @Component public class HelloWorldCommands implements CommandMarker { - protected final Logger LOG = Logger.getLogger(getClass().getName()); - @CliAvailabilityIndicator({"hw echo"}) - public boolean isCommandAvailable() { + private boolean simpleCommandExecuted = false; + + @CliAvailabilityIndicator({"hw simple"}) + public boolean isSimpleAvailable() { + //always available return true; } + @CliAvailabilityIndicator({"hw complex", "hw enum"}) + public boolean isComplexCommandAvailable() { + if (simpleCommandExecuted) { + return true; + } else { + return false; + } + } + + @CliCommand(value = "hw simple", help = "Print a simple hello world message") + public void simple( + @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message, + @CliOption(key = { "location" }, mandatory = false, help = "Where you are saying hello", specifiedDefaultValue="At work") final String location) { + LOG.info("Message = [" + message + "] Location = [" + location + "]"); + simpleCommandExecuted = true; + } + @CliCommand(value = "hw complex", help = "Print a complex hello world message") public void hello( @CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message, diff --git a/src/main/java/org/springframework/shell/core/annotation/CliCommand.java b/src/main/java/org/springframework/shell/core/annotation/CliCommand.java index dc633108..6be3334c 100644 --- a/src/main/java/org/springframework/shell/core/annotation/CliCommand.java +++ b/src/main/java/org/springframework/shell/core/annotation/CliCommand.java @@ -20,6 +20,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * + * Annotates a method that provides a command to the shell. + * + * @author Ben Alex + * @since 1.0 + * + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CliCommand { diff --git a/src/main/java/org/springframework/shell/core/annotation/CliOption.java b/src/main/java/org/springframework/shell/core/annotation/CliOption.java index 1d3ab0c4..ff3ce45b 100644 --- a/src/main/java/org/springframework/shell/core/annotation/CliOption.java +++ b/src/main/java/org/springframework/shell/core/annotation/CliOption.java @@ -22,7 +22,13 @@ import java.lang.annotation.Target; import org.springframework.shell.core.Converter; - +/** + * Annotates the arguments of a command methods, allowing it to declare the argument value as mandatory or optional with a default value. + * + * @author Ben Alex + * @since 1.0 + * + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface CliOption {