initial commit
This commit is contained in:
139
pom.xml
Normal file
139
pom.xml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.samples.spring</groupId>
|
||||
<artifactId>spring-shell</artifactId>
|
||||
<version>1.0.0.CI-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Shell</name>
|
||||
<url>http://www.springframework.org</url>
|
||||
<description>
|
||||
<![CDATA[
|
||||
This project is a minimal jar utility with Spring configuration.
|
||||
]]>
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<jar.mainclass>org.springframework.shell.Bootstrap</jar.mainclass>
|
||||
<maven.test.failure.ignore>true</maven.test.failure.ignore>
|
||||
<spring.framework.version>3.0.6.RELEASE</spring.framework.version>
|
||||
</properties> <dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.framework.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring.framework.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
<version>1.2.14</version>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.springframework.roo</groupId>
|
||||
<artifactId>org.springframework.roo.shell.jline</artifactId>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.roo</groupId>
|
||||
<artifactId>org.springframework.roo.shell</artifactId>
|
||||
<version>1.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
<!-- consider removing dependency -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
|
||||
<!-- External modules -->
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jline</groupId>
|
||||
<artifactId>jline</artifactId>
|
||||
<version>1.0.S2-B</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.fusesource.jansi</groupId>
|
||||
<artifactId>jansi</artifactId>
|
||||
<version>1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.roo.wrapping</groupId>
|
||||
<artifactId>org.springframework.roo.wrapping.json-simple</artifactId>
|
||||
<version>1.1.0.0010</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.5</source>
|
||||
<target>1.5</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>${jar.mainclass}</mainClass>
|
||||
</transformer>
|
||||
<!--
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring.handlers</resource>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
|
||||
<resource>META-INF/spring.schemas</resource>
|
||||
</transformer>
|
||||
-->
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-roo-repository</id>
|
||||
<name>Spring Roo Maven Repository</name>
|
||||
<url>http://spring-roo-repository.springsource.org/release</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
151
src/main/java/org/springframework/shell/Bootstrap.java
Normal file
151
src/main/java/org/springframework/shell/Bootstrap.java
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2011 VMware, Inc. All rights reserved. -- VMware Confidential
|
||||
*/
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
//import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.Converter;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.event.ShellStatus;
|
||||
import org.springframework.roo.support.logging.HandlerUtils;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
//import ch.qos.logback.classic.LoggerContext;
|
||||
//import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
//import ch.qos.logback.core.util.StatusPrinter;
|
||||
|
||||
/**
|
||||
* Main class, needs some cleanup
|
||||
*
|
||||
* @author vnagaraja
|
||||
*/
|
||||
public class Bootstrap {
|
||||
|
||||
private static Bootstrap bootstrap;
|
||||
//TODO using JLineShellComponenet to override and get access to "getSimpleParser" method on the shell - look into autowire...and move back to reference Shell interface.
|
||||
private JLineShellComponent shell;
|
||||
private ConfigurableApplicationContext ctx;
|
||||
private static StopWatch sw = new StopWatch("Spring Sehll");
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
sw.start();
|
||||
SimpleShellCommandLineOptions options = SimpleShellCommandLineOptions.parseCommandLine(args);
|
||||
|
||||
for (Map.Entry<String, String> entry : options.extraSystemProperties.entrySet()) {
|
||||
System.setProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
ExitShellRequest exitShellRequest;
|
||||
try {
|
||||
bootstrap = new Bootstrap(options.applicationContextLocation);
|
||||
exitShellRequest = bootstrap.run(options.executeThenQuit);
|
||||
} catch (RuntimeException t) {
|
||||
throw t;
|
||||
} finally {
|
||||
HandlerUtils.flushAllHandlers(Logger.getLogger(""));
|
||||
}
|
||||
|
||||
System.exit(exitShellRequest.getExitCode());
|
||||
}
|
||||
|
||||
public Bootstrap(String applicationContextLocation) throws IOException {
|
||||
|
||||
//setupLogging();
|
||||
|
||||
Assert.hasText(applicationContextLocation, "Application context location required");
|
||||
|
||||
ctx = new ClassPathXmlApplicationContext(applicationContextLocation);
|
||||
|
||||
|
||||
Map<String, JLineShellComponent> shells = ctx.getBeansOfType(JLineShellComponent.class);
|
||||
|
||||
//Assert.isTrue(shells.size() == 1, "Exactly one Shell was required, but " + shells.size() + " found");
|
||||
//shell = shells.values().iterator().next();
|
||||
|
||||
shell = new JLineShellComponent();
|
||||
|
||||
|
||||
|
||||
Map<String, CommandMarker> commands = ctx.getBeansOfType(CommandMarker.class);
|
||||
|
||||
for (CommandMarker command : commands.values()) {
|
||||
System.out.println("Registgering command " + command);
|
||||
shell.getSimpleParser().add(command);
|
||||
}
|
||||
|
||||
Map<String, Converter> converters = ctx.getBeansOfType(Converter.class);
|
||||
|
||||
for (Converter converter : converters.values()) {
|
||||
System.out.println("Registgering converter " + converter);
|
||||
shell.getSimpleParser().add(converter);
|
||||
}
|
||||
|
||||
|
||||
shell.start();
|
||||
//TODO use listener and latch..
|
||||
while(true) {
|
||||
//System.out.println("shell status = " + shell.getShellStatus().getStatus());
|
||||
if (shell.getShellStatus().getStatus().equals(ShellStatus.Status.USER_INPUT)) {
|
||||
break;
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected ExitShellRequest run(String[] executeThenQuit) {
|
||||
|
||||
ExitShellRequest exitShellRequest;
|
||||
|
||||
if (null != executeThenQuit) {
|
||||
boolean successful = false;
|
||||
exitShellRequest = ExitShellRequest.FATAL_EXIT;
|
||||
|
||||
for(String cmd : executeThenQuit) {
|
||||
successful = shell.executeCommand(cmd);
|
||||
if(!successful)
|
||||
break;
|
||||
}
|
||||
|
||||
//if all commands were successful, set the normal exit status
|
||||
if (successful) {
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
} else {
|
||||
shell.promptLoop();
|
||||
exitShellRequest = shell.getExitShellRequest();
|
||||
if (exitShellRequest == null) {
|
||||
// shouldn't really happen, but we'll fallback to this anyway
|
||||
exitShellRequest = ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
sw.stop();
|
||||
if (shell.isDevelopmentMode()) {
|
||||
System.out.println("Total execution time: " + sw.getLastTaskTimeMillis() + " ms");
|
||||
}
|
||||
return exitShellRequest;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/java/org/springframework/shell/ExampleService.java
Normal file
19
src/main/java/org/springframework/shell/ExampleService.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* {@link Service} with hard-coded input data.
|
||||
*/
|
||||
@Component
|
||||
public class ExampleService implements Service {
|
||||
|
||||
/**
|
||||
* Reads next record from input
|
||||
*/
|
||||
public String getMessage() {
|
||||
return "Hello world!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jline.Completor;
|
||||
|
||||
import org.springframework.roo.shell.Completion;
|
||||
import org.springframework.roo.shell.Parser;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of JLine's {@link Completor} interface that delegates to a {@link Parser}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public class JLineCompletorAdapter implements Completor {
|
||||
|
||||
// Fields
|
||||
private final Parser parser;
|
||||
|
||||
public JLineCompletorAdapter(final Parser parser) {
|
||||
Assert.notNull(parser, "Parser required");
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public int complete(final String buffer, final int cursor, final List candidates) {
|
||||
int result;
|
||||
try {
|
||||
JLineLogHandler.cancelRedrawProhibition();
|
||||
List<Completion> completions = new ArrayList<Completion>();
|
||||
result = parser.completeAdvanced(buffer, cursor, completions);
|
||||
for (Completion completion : completions) {
|
||||
candidates.add(new jline.Completion(completion.getValue(), completion.getFormattedValue(), completion.getHeading()));
|
||||
}
|
||||
} finally {
|
||||
JLineLogHandler.prohibitRedraw();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
213
src/main/java/org/springframework/shell/JLineLogHandler.java
Normal file
213
src/main/java/org/springframework/shell/JLineLogHandler.java
Normal file
@@ -0,0 +1,213 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import jline.ANSIBuffer;
|
||||
import jline.ConsoleReader;
|
||||
|
||||
import org.springframework.roo.shell.ShellPromptAccessor;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.IOUtils;
|
||||
import org.springframework.roo.support.util.OsUtils;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
|
||||
/**
|
||||
* JDK logging {@link Handler} that emits log messages to a JLine {@link ConsoleReader}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public class JLineLogHandler extends Handler {
|
||||
|
||||
// Constants
|
||||
private static final boolean BRIGHT_COLORS = Boolean.getBoolean("roo.bright");
|
||||
|
||||
// Fields
|
||||
private ConsoleReader reader;
|
||||
private ShellPromptAccessor shellPromptAccessor;
|
||||
private static ThreadLocal<Boolean> redrawProhibit = new ThreadLocal<Boolean>();
|
||||
private static String lastMessage;
|
||||
private static boolean includeThreadName = false;
|
||||
private boolean ansiSupported;
|
||||
private String userInterfaceThreadName;
|
||||
private static boolean suppressDuplicateMessages = true;
|
||||
|
||||
public JLineLogHandler(final ConsoleReader reader, final ShellPromptAccessor shellPromptAccessor) {
|
||||
Assert.notNull(reader, "Console reader required");
|
||||
Assert.notNull(shellPromptAccessor, "Shell prompt accessor required");
|
||||
this.reader = reader;
|
||||
this.shellPromptAccessor = shellPromptAccessor;
|
||||
this.userInterfaceThreadName = Thread.currentThread().getName();
|
||||
this.ansiSupported = reader.getTerminal().isANSISupported();
|
||||
|
||||
setFormatter(new Formatter() {
|
||||
@Override
|
||||
public String format(final LogRecord record) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (record.getMessage() != null) {
|
||||
sb.append(record.getMessage()).append(StringUtils.LINE_SEPARATOR);
|
||||
}
|
||||
if (record.getThrown() != null) {
|
||||
PrintWriter pw = null;
|
||||
try {
|
||||
StringWriter sw = new StringWriter();
|
||||
pw = new PrintWriter(sw);
|
||||
record.getThrown().printStackTrace(pw);
|
||||
sb.append(sw.toString());
|
||||
} catch (Exception ex) {
|
||||
} finally {
|
||||
IOUtils.closeQuietly(pw);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void close() throws SecurityException {}
|
||||
|
||||
public static void prohibitRedraw() {
|
||||
redrawProhibit.set(true);
|
||||
}
|
||||
|
||||
public static void cancelRedrawProhibition() {
|
||||
redrawProhibit.remove();
|
||||
}
|
||||
|
||||
public static void setIncludeThreadName(final boolean include) {
|
||||
includeThreadName = include;
|
||||
}
|
||||
|
||||
public static void resetMessageTracking() {
|
||||
lastMessage = null; // see ROO-251
|
||||
}
|
||||
|
||||
public static boolean isSuppressDuplicateMessages() {
|
||||
return suppressDuplicateMessages;
|
||||
}
|
||||
|
||||
public static void setSuppressDuplicateMessages(final boolean suppressDuplicateMessages) {
|
||||
JLineLogHandler.suppressDuplicateMessages = suppressDuplicateMessages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(final LogRecord record) {
|
||||
try {
|
||||
// Avoid repeating the same message that displayed immediately before the current message (ROO-30, ROO-1873)
|
||||
String toDisplay = toDisplay(record);
|
||||
if (toDisplay.equals(lastMessage) && suppressDuplicateMessages) {
|
||||
return;
|
||||
}
|
||||
lastMessage = toDisplay;
|
||||
|
||||
StringBuffer buffer = reader.getCursorBuffer().getBuffer();
|
||||
int cursor = reader.getCursorBuffer().cursor;
|
||||
if (reader.getCursorBuffer().length() > 0) {
|
||||
// The user has semi-typed something, so put a new line in so the debug message is separated
|
||||
reader.printNewline();
|
||||
|
||||
// We need to cancel whatever they typed (it's reset later on), so the line appears empty
|
||||
reader.getCursorBuffer().setBuffer(new StringBuffer());
|
||||
reader.getCursorBuffer().cursor = 0;
|
||||
}
|
||||
|
||||
// This ensures nothing is ever displayed when redrawing the line
|
||||
reader.setDefaultPrompt("");
|
||||
reader.redrawLine();
|
||||
|
||||
// Now restore the line formatting settings back to their original
|
||||
reader.setDefaultPrompt(shellPromptAccessor.getShellPrompt());
|
||||
|
||||
reader.getCursorBuffer().setBuffer(buffer);
|
||||
reader.getCursorBuffer().cursor = cursor;
|
||||
|
||||
reader.printString(toDisplay);
|
||||
|
||||
Boolean prohibitingRedraw = redrawProhibit.get();
|
||||
if (prohibitingRedraw == null) {
|
||||
reader.redrawLine();
|
||||
}
|
||||
|
||||
reader.flushConsole();
|
||||
} catch (Exception e) {
|
||||
reportError("Could not publish log message", e, Level.SEVERE.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
private String toDisplay(final LogRecord event) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String threadName;
|
||||
String eventString;
|
||||
if (includeThreadName && !userInterfaceThreadName.equals(Thread.currentThread().getName()) && !"".equals(Thread.currentThread().getName())) {
|
||||
threadName = "[" + Thread.currentThread().getName() + "]";
|
||||
|
||||
// Build an event string that will indent nicely given the left hand side now contains a thread name
|
||||
StringBuilder lineSeparatorAndIndentingString = new StringBuilder();
|
||||
for (int i = 0; i <= threadName.length(); i++) {
|
||||
lineSeparatorAndIndentingString.append(" ");
|
||||
}
|
||||
|
||||
eventString = " " + getFormatter().format(event).replace(StringUtils.LINE_SEPARATOR, StringUtils.LINE_SEPARATOR + lineSeparatorAndIndentingString.toString());
|
||||
if (eventString.endsWith(lineSeparatorAndIndentingString.toString())) {
|
||||
eventString = eventString.substring(0, eventString.length() - lineSeparatorAndIndentingString.length());
|
||||
}
|
||||
} else {
|
||||
threadName = "";
|
||||
eventString = getFormatter().format(event);
|
||||
}
|
||||
|
||||
if (ansiSupported) {
|
||||
if (event.getLevel().intValue() >= Level.SEVERE.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).red(eventString));
|
||||
} else if (event.getLevel().intValue() >= Level.WARNING.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).magenta(eventString));
|
||||
} else if (event.getLevel().intValue() >= Level.INFO.intValue()) {
|
||||
sb.append(getANSIBuffer().reverse(threadName).green(eventString));
|
||||
} else {
|
||||
sb.append(getANSIBuffer().reverse(threadName).append(eventString));
|
||||
}
|
||||
} else {
|
||||
sb.append(threadName).append(eventString);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes text brighter if requested through system property 'roo.bright' and
|
||||
* works around issue on Windows in using reverse() in combination with the
|
||||
* Jansi lib, which leaves its 'negative' flag set unless reset explicitly.
|
||||
*
|
||||
* @return new patched ANSIBuffer
|
||||
*/
|
||||
static ANSIBuffer getANSIBuffer() {
|
||||
final char esc = (char) 27;
|
||||
return new ANSIBuffer() {
|
||||
@Override
|
||||
public ANSIBuffer reverse(final String str) {
|
||||
if (OsUtils.isWindows()) {
|
||||
return super.reverse(str).append(ANSICodes.attrib(esc));
|
||||
}
|
||||
return super.reverse(str);
|
||||
};
|
||||
@Override
|
||||
public ANSIBuffer attrib(final String str, final int code) {
|
||||
if (BRIGHT_COLORS && 30 <= code && code <= 37) {
|
||||
// This is a color code: add a 'bright' code
|
||||
return append(esc + "[" + code + ";1m").append(str).append(ANSICodes.attrib(0));
|
||||
}
|
||||
return super.attrib(str, code);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
498
src/main/java/org/springframework/shell/JLineShell.java
Normal file
498
src/main/java/org/springframework/shell/JLineShell.java
Normal file
@@ -0,0 +1,498 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jline.ANSIBuffer;
|
||||
import jline.ANSIBuffer.ANSICodes;
|
||||
import jline.ConsoleReader;
|
||||
import jline.WindowsTerminal;
|
||||
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.event.ShellStatus;
|
||||
import org.springframework.roo.shell.event.ShellStatus.Status;
|
||||
import org.springframework.roo.shell.event.ShellStatusListener;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.ClassUtils;
|
||||
import org.springframework.roo.support.util.FileCopyUtils;
|
||||
import org.springframework.roo.support.util.IOUtils;
|
||||
import org.springframework.roo.support.util.OsUtils;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
import org.springframework.shell.commands.HintCommands;
|
||||
|
||||
|
||||
/**
|
||||
* Uses the feature-rich <a href="http://sourceforge.net/projects/jline/">JLine</a> library to provide an interactive shell.
|
||||
*
|
||||
* <p>
|
||||
* Due to Windows' lack of color ANSI services out-of-the-box, this implementation automatically detects the classpath
|
||||
* presence of <a href="http://jansi.fusesource.org/">Jansi</a> and uses it if present. This library is not necessary
|
||||
* for *nix machines, which support colour ANSI without any special effort. This implementation has been written to
|
||||
* use reflection in order to avoid hard dependencies on Jansi.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.0
|
||||
*/
|
||||
public abstract class JLineShell extends AbstractShell implements CommandMarker, Shell, Runnable {
|
||||
|
||||
// Constants
|
||||
private static final String ANSI_CONSOLE_CLASSNAME = "org.fusesource.jansi.AnsiConsole";
|
||||
private static final boolean JANSI_AVAILABLE = ClassUtils.isPresent(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader());
|
||||
private static final boolean APPLE_TERMINAL = Boolean.getBoolean("is.apple.terminal");
|
||||
private static final char ESCAPE = 27;
|
||||
private static final String BEL = "\007";
|
||||
|
||||
//TODO make configurable
|
||||
private static final String COMMAND_LINE_PROMPT = "spring>";
|
||||
|
||||
// Fields
|
||||
private ConsoleReader reader;
|
||||
private boolean developmentMode = false;
|
||||
private FileWriter fileLog;
|
||||
private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
protected ShellStatusListener statusListener; // ROO-836
|
||||
/** key: slot name, value: flashInfo instance */
|
||||
private final Map<String, FlashInfo> flashInfoMap = new HashMap<String, FlashInfo>();
|
||||
/** key: row number, value: eraseLineFromPosition */
|
||||
private final Map<Integer, Integer> rowErasureMap = new HashMap<Integer, Integer>();
|
||||
private boolean shutdownHookFired = false; // ROO-1599
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (JANSI_AVAILABLE && OsUtils.isWindows()) {
|
||||
try {
|
||||
reader = createAnsiWindowsReader();
|
||||
} catch (Exception e) {
|
||||
// Try again using default ConsoleReader constructor
|
||||
logger.warning("Can't initialize jansi AnsiConsole, falling back to default: " + e);
|
||||
}
|
||||
}
|
||||
if (reader == null) {
|
||||
reader = new ConsoleReader();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Cannot start console class", ioe);
|
||||
}
|
||||
|
||||
setPromptPath(null);
|
||||
|
||||
JLineLogHandler handler = new JLineLogHandler(reader, this);
|
||||
JLineLogHandler.prohibitRedraw(); // Affects this thread only
|
||||
Logger mainLogger = Logger.getLogger("");
|
||||
removeHandlers(mainLogger);
|
||||
mainLogger.addHandler(handler);
|
||||
|
||||
reader.addCompletor(new JLineCompletorAdapter(getParser()));
|
||||
|
||||
reader.setBellEnabled(true);
|
||||
if (Boolean.getBoolean("jline.nobell")) {
|
||||
reader.setBellEnabled(false);
|
||||
}
|
||||
|
||||
// reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true)));
|
||||
|
||||
openFileLogIfPossible();
|
||||
|
||||
// Try to build previous command history from the project's log
|
||||
try {
|
||||
String logFileContents = FileCopyUtils.copyToString(new File("log.roo"));
|
||||
String[] logEntries = logFileContents.split(StringUtils.LINE_SEPARATOR);
|
||||
// LIFO
|
||||
for (String logEntry : logEntries) {
|
||||
if (!logEntry.startsWith("//")) {
|
||||
reader.getHistory().addToHistory(logEntry);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
flashMessageRenderer();
|
||||
|
||||
logger.info(version(null));
|
||||
|
||||
flash(Level.FINE, "Spring Roo " + versionInfo(), Shell.WINDOW_TITLE_SLOT);
|
||||
|
||||
|
||||
//TODO make this configurable
|
||||
logger.info("Welcome to Spring Shell. For assistance press " + completionKeys + " or type \"hint\" then hit ENTER.");
|
||||
|
||||
String startupNotifications = getStartupNotifications();
|
||||
if (StringUtils.hasText(startupNotifications)) {
|
||||
logger.info(startupNotifications);
|
||||
}
|
||||
|
||||
setShellStatus(Status.STARTED);
|
||||
|
||||
// Monitor CTRL+C initiated shutdowns (ROO-1599)
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
shutdownHookFired = true;
|
||||
// We don't need to closeShell(), as the shutdown hook in o.s.r.bootstrap.Main calls stop() which calls JLineShellComponent.deactivate() and that calls closeShell()
|
||||
}
|
||||
}, "Spring Roo JLine Shutdown Hook"));
|
||||
|
||||
// Handle any "execute-then-quit" operation
|
||||
String rooArgs = System.getProperty("roo.args");
|
||||
if (rooArgs != null && !"".equals(rooArgs)) {
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
boolean success = executeCommand(rooArgs);
|
||||
if (exitShellRequest == null) {
|
||||
// The command itself did not specify an exit shell code, so we'll fall back to something sensible here
|
||||
executeCommand("quit"); // ROO-839
|
||||
exitShellRequest = success ? ExitShellRequest.NORMAL_EXIT : ExitShellRequest.FATAL_EXIT;
|
||||
}
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
} else {
|
||||
// Normal RPEL processing
|
||||
promptLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public String getStartupNotifications() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void removeHandlers(final Logger l) {
|
||||
Handler[] handlers = l.getHandlers();
|
||||
if (handlers != null && handlers.length > 0) {
|
||||
for (Handler h : handlers) {
|
||||
l.removeHandler(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPromptPath(final String path) {
|
||||
setPromptPath(path, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPromptPath(final String path, final boolean overrideStyle) {
|
||||
if (reader.getTerminal().isANSISupported()) {
|
||||
ANSIBuffer ansi = JLineLogHandler.getANSIBuffer();
|
||||
if (path == null || "".equals(path)) {
|
||||
shellPrompt = ansi.yellow(COMMAND_LINE_PROMPT).toString();
|
||||
} else {
|
||||
if (overrideStyle) {
|
||||
ansi.append(path);
|
||||
} else {
|
||||
ansi.cyan(path);
|
||||
}
|
||||
shellPrompt = ansi.yellow(" " + COMMAND_LINE_PROMPT).toString();
|
||||
}
|
||||
} else {
|
||||
// The superclass will do for this non-ANSI terminal
|
||||
super.setPromptPath(path);
|
||||
}
|
||||
|
||||
// The shellPrompt is now correct; let's ensure it now gets used
|
||||
reader.setDefaultPrompt(JLineShell.shellPrompt);
|
||||
}
|
||||
|
||||
private ConsoleReader createAnsiWindowsReader() throws Exception {
|
||||
// Get decorated OutputStream that parses ANSI-codes
|
||||
final PrintStream ansiOut = (PrintStream) ClassUtils.forName(ANSI_CONSOLE_CLASSNAME, JLineShell.class.getClassLoader()).getMethod("out").invoke(null);
|
||||
WindowsTerminal ansiTerminal = new WindowsTerminal() {
|
||||
@Override
|
||||
public boolean isANSISupported() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
ansiTerminal.initializeTerminal();
|
||||
// Make sure to reset the original shell's colors on shutdown by closing the stream
|
||||
statusListener = new ShellStatusListener() {
|
||||
public void onShellStatusChange(final ShellStatus oldStatus, final ShellStatus newStatus) {
|
||||
if (newStatus.getStatus().equals(Status.SHUTTING_DOWN)) {
|
||||
ansiOut.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
addShellStatusListener(statusListener);
|
||||
|
||||
return new ConsoleReader(new FileInputStream(FileDescriptor.in), new PrintWriter(new OutputStreamWriter(ansiOut,
|
||||
// Default to Cp850 encoding for Windows console output (ROO-439)
|
||||
System.getProperty("jline.WindowsTerminal.output.encoding", "Cp850"))), null, ansiTerminal);
|
||||
}
|
||||
|
||||
private void flashMessageRenderer() {
|
||||
if (!reader.getTerminal().isANSISupported()) {
|
||||
return;
|
||||
}
|
||||
// Setup a thread to ensure flash messages are displayed and cleared correctly
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
while (!shellStatus.getStatus().equals(Status.SHUTTING_DOWN) && !shutdownHookFired) {
|
||||
synchronized (flashInfoMap) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Set<String> toRemove = new HashSet<String>();
|
||||
for (String slot : flashInfoMap.keySet()) {
|
||||
FlashInfo flashInfo = flashInfoMap.get(slot);
|
||||
|
||||
if (flashInfo.flashMessageUntil < now) {
|
||||
// Message has expired, so clear it
|
||||
toRemove.add(slot);
|
||||
doAnsiFlash(flashInfo.rowNumber, Level.ALL, "");
|
||||
} else {
|
||||
// The expiration time for this message has not been reached, so preserve it
|
||||
doAnsiFlash(flashInfo.rowNumber, flashInfo.flashLevel, flashInfo.flashMessage);
|
||||
}
|
||||
}
|
||||
for (String slot : toRemove) {
|
||||
flashInfoMap.remove(slot);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "Spring Roo JLine Flash Message Manager");
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flash(final Level level, final String message, final String slot) {
|
||||
Assert.notNull(level, "Level is required for a flash message");
|
||||
Assert.notNull(message, "Message is required for a flash message");
|
||||
Assert.hasText(slot, "Slot name must be specified for a flash message");
|
||||
|
||||
if (Shell.WINDOW_TITLE_SLOT.equals(slot)) {
|
||||
if (reader != null && reader.getTerminal().isANSISupported()) {
|
||||
// We can probably update the window title, as requested
|
||||
if (StringUtils.isBlank(message)) {
|
||||
System.out.println("No text");
|
||||
}
|
||||
|
||||
ANSIBuffer buff = JLineLogHandler.getANSIBuffer();
|
||||
buff.append(ESCAPE + "]0;").append(message).append(BEL);
|
||||
String stg = buff.toString();
|
||||
try {
|
||||
reader.printString(stg);
|
||||
reader.flushConsole();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
if ((reader != null && !reader.getTerminal().isANSISupported())) {
|
||||
super.flash(level, message, slot);
|
||||
return;
|
||||
}
|
||||
synchronized (flashInfoMap) {
|
||||
FlashInfo flashInfo = flashInfoMap.get(slot);
|
||||
|
||||
if ("".equals(message)) {
|
||||
// Request to clear the message, but give the user some time to read it first
|
||||
if (flashInfo == null) {
|
||||
// We didn't have a record of displaying it in the first place, so just quit
|
||||
return;
|
||||
}
|
||||
flashInfo.flashMessageUntil = System.currentTimeMillis() + 1500;
|
||||
} else {
|
||||
// Display this message displayed until further notice
|
||||
if (flashInfo == null) {
|
||||
// Find a row for this new slot; we basically take the first line number we discover
|
||||
flashInfo = new FlashInfo();
|
||||
flashInfo.rowNumber = Integer.MAX_VALUE;
|
||||
outer: for (int i = 1; i < Integer.MAX_VALUE; i++) {
|
||||
for (FlashInfo existingFlashInfo : flashInfoMap.values()) {
|
||||
if (existingFlashInfo.rowNumber == i) {
|
||||
// Veto, let's try the new candidate row number
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
// If we got to here, nobody owns this row number, so use it
|
||||
flashInfo.rowNumber = i;
|
||||
break outer;
|
||||
}
|
||||
|
||||
// Store it
|
||||
flashInfoMap.put(slot, flashInfo);
|
||||
}
|
||||
// Populate the instance with the latest data
|
||||
flashInfo.flashMessageUntil = Long.MAX_VALUE;
|
||||
flashInfo.flashLevel = level;
|
||||
flashInfo.flashMessage = message;
|
||||
|
||||
// Display right now
|
||||
doAnsiFlash(flashInfo.rowNumber, flashInfo.flashLevel, flashInfo.flashMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Externally synchronized via the two calling methods having a mutex on flashInfoMap
|
||||
private void doAnsiFlash(final int row, final Level level, final String message) {
|
||||
ANSIBuffer buff = JLineLogHandler.getANSIBuffer();
|
||||
if (APPLE_TERMINAL) {
|
||||
buff.append(ESCAPE + "7");
|
||||
} else {
|
||||
buff.append(ANSICodes.save());
|
||||
}
|
||||
|
||||
// Figure out the longest line we're presently displaying (or were) and erase the line from that position
|
||||
int mostFurtherLeftColNumber = Integer.MAX_VALUE;
|
||||
for (Integer candidate : rowErasureMap.values()) {
|
||||
if (candidate < mostFurtherLeftColNumber) {
|
||||
mostFurtherLeftColNumber = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (mostFurtherLeftColNumber == Integer.MAX_VALUE) {
|
||||
// There is nothing to erase
|
||||
} else {
|
||||
buff.append(ANSICodes.gotoxy(row, mostFurtherLeftColNumber));
|
||||
buff.append(ANSICodes.clreol()); // Clear what was present on the line
|
||||
}
|
||||
|
||||
if (("".equals(message))) {
|
||||
// They want the line blank; we've already achieved this if needed via the erasing above
|
||||
// Just need to record we no longer care about this line the next time doAnsiFlash is invoked
|
||||
rowErasureMap.remove(row);
|
||||
} else {
|
||||
if (shutdownHookFired) {
|
||||
return; // ROO-1599
|
||||
}
|
||||
// They want some message displayed
|
||||
int startFrom = reader.getTermwidth() - message.length() + 1;
|
||||
if (startFrom < 1) {
|
||||
startFrom = 1;
|
||||
}
|
||||
buff.append(ANSICodes.gotoxy(row, startFrom));
|
||||
buff.reverse(message);
|
||||
// Record we want to erase from this positioning next time (so we clean up after ourselves)
|
||||
rowErasureMap.put(row, startFrom);
|
||||
}
|
||||
if (APPLE_TERMINAL) {
|
||||
buff.append(ESCAPE + "8");
|
||||
} else {
|
||||
buff.append(ANSICodes.restore());
|
||||
}
|
||||
|
||||
String stg = buff.toString();
|
||||
try {
|
||||
reader.printString(stg);
|
||||
reader.flushConsole();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public void promptLoop() {
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
String line;
|
||||
|
||||
try {
|
||||
while (exitShellRequest == null && ((line = reader.readLine()) != null)) {
|
||||
JLineLogHandler.resetMessageTracking();
|
||||
setShellStatus(Status.USER_INPUT);
|
||||
|
||||
if ("".equals(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
executeCommand(line);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Shell line reading failure", ioe);
|
||||
}
|
||||
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
}
|
||||
|
||||
public void setDevelopmentMode(final boolean developmentMode) {
|
||||
JLineLogHandler.setIncludeThreadName(developmentMode);
|
||||
JLineLogHandler.setSuppressDuplicateMessages(!developmentMode); // We want to see duplicate messages during development time (ROO-1873)
|
||||
this.developmentMode = developmentMode;
|
||||
}
|
||||
|
||||
public boolean isDevelopmentMode() {
|
||||
return this.developmentMode;
|
||||
}
|
||||
|
||||
private void openFileLogIfPossible() {
|
||||
try {
|
||||
fileLog = new FileWriter("log.roo", true);
|
||||
// First write, so let's record the date and time of the first user command
|
||||
fileLog.write("// Spring Roo " + versionInfo() + " log opened at " + df.format(new Date()) + "\n");
|
||||
fileLog.flush();
|
||||
} catch (IOException ignoreIt) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void logCommandToOutput(final String processedLine) {
|
||||
if (fileLog == null) {
|
||||
openFileLogIfPossible();
|
||||
if (fileLog == null) {
|
||||
// Still failing, so give up
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
fileLog.write(processedLine + "\n"); // Unix line endings only from Roo
|
||||
fileLog.flush(); // So tail -f will show it's working
|
||||
if (getExitShellRequest() != null) {
|
||||
// Shutting down, so close our file (we can always reopen it later if needed)
|
||||
fileLog.write("// Spring Roo " + versionInfo() + " log closed at " + df.format(new Date()) + "\n");
|
||||
IOUtils.closeQuietly(fileLog);
|
||||
fileLog = null;
|
||||
}
|
||||
} catch (IOException ignoreIt) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the "roo.home" from the system property, falling back to the current working directory if missing.
|
||||
*
|
||||
* @return the 'roo.home' system property
|
||||
*/
|
||||
@Override
|
||||
protected String getHomeAsString() {
|
||||
String rooHome = System.getProperty("roo.home");
|
||||
if (rooHome == null) {
|
||||
try {
|
||||
rooHome = new File(".").getCanonicalPath();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return rooHome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called by a subclass before deactivating the shell.
|
||||
*/
|
||||
protected void closeShell() {
|
||||
// Notify we're closing down (normally our status is already shutting_down, but if it was a CTRL+C via the o.s.r.bootstrap.Main hook)
|
||||
setShellStatus(Status.SHUTTING_DOWN);
|
||||
if (statusListener != null) {
|
||||
removeShellStatusListener(statusListener);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FlashInfo {
|
||||
String flashMessage;
|
||||
long flashMessageUntil;
|
||||
Level flashLevel;
|
||||
int rowNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import static org.springframework.roo.support.util.StringUtils.LINE_SEPARATOR;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.ExecutionStrategy;
|
||||
import org.springframework.roo.shell.Parser;
|
||||
import org.springframework.roo.shell.SimpleParser;
|
||||
|
||||
/**
|
||||
* Launcher for {@link JLineShell}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.1
|
||||
*/
|
||||
public class JLineShellComponent extends JLineShell implements Lifecycle {
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
// Fields
|
||||
private ExecutionStrategy executionStrategy = new SimpleExecutionStrategy(); //ProcessManagerHostedExecutionStrategy is not what i think we need outside of Roo.
|
||||
private SimpleParser parser = new SimpleParser();
|
||||
|
||||
// Dont' need this, used to get twitter status.
|
||||
//@Reference private UrlInputStreamService urlInputStreamService;
|
||||
//
|
||||
|
||||
|
||||
public SimpleParser getSimpleParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Thread thread = new Thread(this, "Spring Roo JLine Shell");
|
||||
thread.start();
|
||||
running=true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
closeShell();
|
||||
running=false;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<URL> findResources(final String path) {
|
||||
// For an OSGi bundle search, we add the root prefix to the given path
|
||||
throw new UnsupportedOperationException("TODO: need to use standard classpath search");
|
||||
//return OSGiUtils.findEntriesByPath(context.getBundleContext(), OSGiUtils.ROOT_PATH + path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExecutionStrategy getExecutionStrategy() {
|
||||
return executionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parser getParser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStartupNotifications() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@CliCommand(value = { "version" }, help = "Displays shell version")
|
||||
public String version(@CliOption(key = "", help = "Special version flags") final String extra) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(" _____ _ ").append(LINE_SEPARATOR);
|
||||
sb.append("/ ___| (_)").append(LINE_SEPARATOR);
|
||||
sb.append("\\ `--, _ __ _ __ _ _ __ __ _ ").append(LINE_SEPARATOR);
|
||||
sb.append(" `--. \\ '_ \\| '__| | '_ \\ / _` |").append(LINE_SEPARATOR);
|
||||
sb.append("/\\__/ / |_) | | | | | | | (_| |").append(LINE_SEPARATOR);
|
||||
sb.append("\\____/| .__/|_| |_|_| |_|\\__, |").append(LINE_SEPARATOR);
|
||||
sb.append(" | | __/ |").append(LINE_SEPARATOR);
|
||||
sb.append(" |_| |___/ ").append(" ").append(versionInfo()).append(LINE_SEPARATOR);
|
||||
sb.append(LINE_SEPARATOR);
|
||||
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
7
src/main/java/org/springframework/shell/Service.java
Normal file
7
src/main/java/org/springframework/shell/Service.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
public interface Service {
|
||||
|
||||
String getMessage();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
import org.springframework.roo.shell.ExecutionStrategy;
|
||||
import org.springframework.roo.shell.ParseResult;
|
||||
import org.springframework.roo.support.util.Assert;
|
||||
import org.springframework.roo.support.util.ReflectionUtils;
|
||||
|
||||
public class SimpleExecutionStrategy implements ExecutionStrategy {
|
||||
|
||||
private final Class<?> mutex = SimpleExecutionStrategy.class;
|
||||
|
||||
public Object execute(ParseResult parseResult) throws RuntimeException {
|
||||
Assert.notNull(parseResult, "Parse result required");
|
||||
synchronized (mutex) {
|
||||
Assert.isTrue(isReadyForCommands(), "ProcessManagerHostedExecutionStrategy not yet ready for commands");
|
||||
return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments());
|
||||
/*
|
||||
Assert.isTrue(isReadyForCommands(), "ProcessManagerHostedExecutionStrategy not yet ready for commands");
|
||||
return processManager.execute(new CommandCallback<Object>() {
|
||||
public Object callback() {
|
||||
return ReflectionUtils.invokeMethod(parseResult.getMethod(), parseResult.getInstance(), parseResult.getArguments());
|
||||
}
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReadyForCommands() {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2011 VMware, Inc. All rights reserved. -- VMware Confidential
|
||||
*/
|
||||
package org.springframework.shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/**
|
||||
* Not really used much, but keeping for future use
|
||||
*
|
||||
* @author vnagaraja
|
||||
*/
|
||||
public class SimpleShellCommandLineOptions {
|
||||
|
||||
public static final String DEFAULT_APP_CTX = "classpath*:/META-INF/spring/app-context.xml";
|
||||
String applicationContextLocation = DEFAULT_APP_CTX;
|
||||
String[] executeThenQuit = null;
|
||||
Map<String, String> extraSystemProperties = new HashMap<String, String>();
|
||||
|
||||
public static SimpleShellCommandLineOptions parseCommandLine(String[] args) throws IOException {
|
||||
SimpleShellCommandLineOptions options = new SimpleShellCommandLineOptions();
|
||||
List<String> commands = new ArrayList<String>();
|
||||
int i = 0;
|
||||
while (i < args.length) {
|
||||
String arg = args[i++];
|
||||
if (arg.equals("-environment")) {
|
||||
String environment = args[i++];
|
||||
options.extraSystemProperties.put("napa.application.profile", environment);
|
||||
} else if (arg.equals("-ctx")) {
|
||||
options.applicationContextLocation = args[i++];
|
||||
} else if (arg.equals("-cmdfile")) {
|
||||
File f = new File(args[i++]);
|
||||
commands.addAll(FileUtils.readLines(f));
|
||||
} else {
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (; i < args.length; i++) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(args[i]);
|
||||
}
|
||||
|
||||
if(sb.length()>0) {
|
||||
String[] cmdLineCommands = sb.toString().split(";");
|
||||
for(String s : cmdLineCommands) {
|
||||
//add any command line commands after the commands loaded from the file
|
||||
commands.add(s.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if(commands.size()>0)
|
||||
options.executeThenQuit = commands.toArray(new String[commands.size()]);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.roo.shell.ExitShellRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class EssentialCommands implements CommandMarker {
|
||||
|
||||
@CliCommand(value={"exit", "quit"}, help="Exits the shell")
|
||||
public ExitShellRequest quit() {
|
||||
return ExitShellRequest.NORMAL_EXIT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.roo.shell.CliAvailabilityIndicator;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HelloWorldCommands implements CommandMarker {
|
||||
|
||||
|
||||
@CliAvailabilityIndicator({"hw help"})
|
||||
public boolean isCommandAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@CliCommand(value = "hw echo", help = "Print a hello world message")
|
||||
public void config(
|
||||
@CliOption(key = { "message" }, mandatory = true, help = "The hello world message") final String message) {
|
||||
System.out.println("Hello world " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.CliCommand;
|
||||
import org.springframework.roo.shell.CliOption;
|
||||
import org.springframework.roo.shell.CommandMarker;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class HintCommands implements CommandMarker {
|
||||
|
||||
// Fields
|
||||
@Autowired private HintOperations hintOperations;
|
||||
|
||||
@CliCommand(value = "hint", help = "Provides step-by-step hints and context-sensitive guidance")
|
||||
public String hint(
|
||||
@CliOption(key = { "topic", "" }, mandatory = false, unspecifiedDefaultValue = "", optionContext = "disable-string-converter,topics", help = "The topic for which advice should be provided") final String topic) {
|
||||
|
||||
return hintOperations.hint(topic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.Completion;
|
||||
import org.springframework.roo.shell.Converter;
|
||||
import org.springframework.roo.shell.MethodTarget;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* {@link Converter} for {@link String} that understands the "topics" option context.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @since 1.1
|
||||
*/
|
||||
@Component
|
||||
public class HintConverter implements Converter<String> {
|
||||
|
||||
// Fields
|
||||
@Autowired private HintOperations hintOperations;
|
||||
|
||||
public String convertFromText(final String value, final Class<?> requiredType, final String optionContext) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String existingData, final String optionContext, final MethodTarget target) {
|
||||
for (String currentTopic : hintOperations.getCurrentTopics()) {
|
||||
completions.add(new Completion(currentTopic));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supports(final Class<?> requiredType, final String optionContext) {
|
||||
return String.class.isAssignableFrom(requiredType) && optionContext.contains("topics");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.SortedSet;
|
||||
|
||||
public interface HintOperations {
|
||||
|
||||
String hint(String topic);
|
||||
|
||||
SortedSet<String> getCurrentTopics();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.springframework.shell.commands;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.roo.shell.AbstractShell;
|
||||
import org.springframework.roo.support.util.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ResourceBundleHintOperations implements HintOperations {
|
||||
|
||||
private static ResourceBundle bundle = ResourceBundle.getBundle(HintCommands.class.getName());
|
||||
|
||||
public String hint(String topic) {
|
||||
if (StringUtils.isBlank(topic)) {
|
||||
topic = determineTopic();
|
||||
}
|
||||
try {
|
||||
String message = bundle.getString(topic);
|
||||
return message.replace("\r", StringUtils.LINE_SEPARATOR).replace("${completion_key}", AbstractShell.completionKeys);
|
||||
} catch (MissingResourceException exception) {
|
||||
return "Cannot find topic '" + topic + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SortedSet<String> getCurrentTopics() {
|
||||
SortedSet<String> result = new TreeSet<String>();
|
||||
String topic = determineTopic();
|
||||
if ("general".equals(topic)) {
|
||||
for (Enumeration<String> keys = bundle.getKeys(); keys.hasMoreElements();) {
|
||||
result.add(keys.nextElement());
|
||||
}
|
||||
// result.addAll(bundle.keySet()); ResourceBundle.keySet() method in JDK 6+
|
||||
} else {
|
||||
result.add(topic);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String determineTopic() {
|
||||
return "start";
|
||||
//return "general";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.springframework.shell.converters;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.roo.shell.Shell;
|
||||
import org.springframework.roo.shell.converters.FileConverter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
//@Component
|
||||
public class SimpleFileConverter extends FileConverter {
|
||||
@Autowired private Shell shell;
|
||||
|
||||
@Override
|
||||
protected File getWorkingDirectory() {
|
||||
return shell.getHome();
|
||||
}
|
||||
}
|
||||
29
src/main/resources/META-INF/spring/app-context.xml
Normal file
29
src/main/resources/META-INF/spring/app-context.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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-3.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
|
||||
|
||||
<description>Example configuration to get you started.</description>
|
||||
|
||||
<context:component-scan base-package="org.springframework.shell" />
|
||||
|
||||
<bean class="org.springframework.roo.shell.converters.StringConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.AvailableCommandsConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.BigDecimalConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.BigIntegerConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.BooleanConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.CharacterConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.DateConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.DoubleConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.EnumConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.FloatConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.IntegerConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.LocaleConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.LongConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.ShortConverter"/>
|
||||
<bean class="org.springframework.roo.shell.converters.StaticFieldConverterImpl"/>
|
||||
<bean class="org.springframework.roo.shell.converters.StringConverter"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,14 @@
|
||||
topics=The following hints are available to help you use the vFabric shell:\r\r general, start, persistence, entities, fields, relationships, \r web mvc, finders, eclipse, logging, gwt\r\rJust type 'hint topic_name' (without quotes) to view a specific hint.
|
||||
|
||||
general=At this stage of the project, you have a few options:\r\r * List all hint topics via 'hint topics'\r * Create more fields with 'hint fields'\r * Create more entities with 'hint entities'\r * Create a web controller with 'hint web mvc'\r * Create dynamic finders with 'hint finders'\r * Setup your logging levels via 'hint logging'\r * Run tests via Maven (type 'perform tests')\r * Build a deployment artifact (type 'perform package')\r * Learn about Eclipse integration by typing 'hint eclipse'\r * Add support for Google Web Toolkit via 'hint gwt'\r * Discover all Roo commands by typing 'help'
|
||||
|
||||
start=Welcome to the Spring Shell! To start your journey.....
|
||||
persistence=Roo requires the installation of a persistence configuration,\rfor example, JPA or MongoDB.\r\rFor JPA, type 'jpa setup' and then hit ${completion_key} three times.\rWe suggest you type 'H' then ${completion_key} to complete "HIBERNATE".\rAfter the --provider, press ${completion_key} twice for database choices.\rFor testing purposes, type (or ${completion_key}) HYPERSONIC_IN_MEMORY.\rIf you press ${completion_key} again, you'll see there are no more options.\rAs such, you're ready to press ENTER to execute the command.\r\rOnce JPA is installed, type 'hint' and ENTER for the next suggestion.\n\nSimilarly, for MongoDB persistence, type 'mongo setup' and ENTER.
|
||||
entities=You can create entities either via Roo or your IDE.\nUsing the Roo shell is fast and easy, especially thanks to the ${completion_key} completion.\r\rStart by typing 'ent' and then hitting ${completion_key} twice.\rEnter the --class in the form '~.domain.MyEntityClassName'\rIn Roo, '~' means the --topLevelPackage you specified via 'create project'.\r\rAfter specify a --class argument, press SPACE then ${completion_key}. Note nothing appears.\rBecause nothing appears, it means you've entered all mandatory arguments.\rHowever, optional arguments do exist for this command (and most others in Roo).\rTo see the optional arguments, type '--' and then hit ${completion_key}. Mostly you won't\nneed any optional arguments, but let's select the --testAutomatically option\nand hit ENTER. You can always use this approach to view optional arguments.\r\rAfter creating an entity, use 'hint' for the next suggestion.
|
||||
fields=You can add fields to your entities using either Roo or your IDE.\n\nTo add a new field, type 'field' and then hit ${completion_key}. Be sure to select\nyour entity and provide a legal Java field name. Use ${completion_key} to find an entity\nname, and '~' to refer to the top level package. Also remember to use ${completion_key}\nto access each mandatory argument for the command.\n\nAfter completing the mandatory arguments, press SPACE, type '--' and then ${completion_key}.\nThe optional arguments shown reflect official JSR 303 Validation constraints.\nFeel free to use an optional argument, or delete '--' and hit ENTER.\n\nIf creating multiple fields, use the UP arrow to access command history.\n\nAfter adding your fields, type 'hint' for the next suggestion.\nTo learn about setting up many-to-one fields, type 'hint relationships'.
|
||||
relationships=You create persistent relationships via 'field set' and 'field reference'.\n\nFor example, consider the typical Order <-> LineItem case:\n\n ENTITY: Order ENTITY: LineItem\n Set<LineItem> items Order order\n\nTo setup this relationship in Roo, you would use:\n\nfield set --fieldName items --class Order --type LineItem --mappedBy order\nfield reference --fieldName order --class LineItem --type Order --notNull\n\nLearn about fields addition using 'hint fields'.
|
||||
web\ mvc=Creating RESTful Spring MVC web controllers is quick and easy using Roo.\nControllers can be made that automatically expose an entity. Alternately, we\ncan create a stub, empty controller for you to finish off.\n\nFor the former, type 'web mvc setup' and hit ENTER followed by 'web mvc scaffold' and hit ${completion_key} three times.\nThe --class is the controller name; it need not reflect an entity name.\nWe suggest putting controllers under a '~.web' package to improve maintenance.\nYou can also specify the --backingType the controller should expose.\n\nAfter creating a controller, use 'hint' for further suggestions.
|
||||
finders=Roo can automatically create complex, type-safe finder methods.\n\nBe sure to add fields to your entity before creating finders.\nLearn how to add fields by typing 'hint fields'.\n\nTo view available finders, type 'finder list' then ${completion_key}.\nNext select your entity class and hit ENTER. Names are then displayed.\nYou can see even more finder combinations by using --depth 2 or higher.\n\nTo add a finder, type 'finder add' and hit ${completion_key} twice.\nSpecify a --finderName from the earlier displayed output of 'list finders for'.\n\nFor more hints, type 'hint' and hit ENTER.
|
||||
eclipse=It's easy to use your project in Eclipse or SpringSource Tool Suite (STS).\r\rTo set this up, you'll need to use the command prompt and then Eclipse itself:\r\r 1. Start by typing 'exit', to leave the Roo shell\r 2. Type 'mvn eclipse:clean eclipse:eclipse' to create Eclipse project files\r 3. Load Eclipse, then File > Import > Existing Projects Into Workspace\r 4. Ensure AJDT 1.6.5 or above is installed in Eclipse\r 5. Enable Window > Preferences > General > Workspace > Refresh Automatically\r 6. Finally, restart Roo (type 'roo' at the OS prompt) and enter 'hint'\r\rPlease note if you have the m2eclipse plugin installed, you need only select\rImport > Maven Projects from the Eclipse Import menu.\r\rFor the best Eclipse experience, we recommend SpringSource Tool Suite (STS):\r\r * Graphical editing of Roo commands\r * No need to use the Roo shell\r * Immediate importing of Roo projects\r * Full Spring projects integration\r * Many other productivity-increasing features\r\rDownload STS from http://springsource.com (it's free!).\r\rYou can also use 'perform eclipse' instead of leaving the Roo interface.
|
||||
logging=You can easily configure logging levels in your project.\r\rRoo will update the log4j.properties file to control your logging.\r\rType 'logging setup' then hit ${completion_key} twice. We suggest 'DEBUG' level.\rYou may wish to specify the optional --package argument (defaults to 'ALL').\r\rRemember to type 'hint' for further hints and suggestions.
|
||||
gwt=It's easy to create a GWT client in your project.\r\rJust type 'web gwt setup' and press ENTER.\r\rNote Roo's GWT support outputs GWT 2.2 applications.
|
||||
25
src/site/site.xml
Normal file
25
src/site/site.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project name="Spring Batch: ${project.name}">
|
||||
|
||||
<bannerLeft>
|
||||
<name>Spring Sample: ${project.name}</name>
|
||||
<href>index.html</href>
|
||||
</bannerLeft>
|
||||
|
||||
<skin>
|
||||
<groupId>org.springframework.maven.skins</groupId>
|
||||
<artifactId>maven-spring-skin</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</skin>
|
||||
|
||||
<body>
|
||||
|
||||
<links>
|
||||
<item name="${project.name}" href="index.html"/>
|
||||
</links>
|
||||
|
||||
<menu ref="reports"/>
|
||||
|
||||
</body>
|
||||
|
||||
</project>
|
||||
6
src/test/resources/log4j.properties
Normal file
6
src/test/resources/log4j.properties
Normal file
@@ -0,0 +1,6 @@
|
||||
log4j.rootCategory=INFO, stdout
|
||||
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
|
||||
|
||||
Reference in New Issue
Block a user