Commit fb6b2244 authored by Dave Syer's avatar Dave Syer

Fast forward existing prototype work

parent 80b151e2
.gradle
*.sw?
.#*
*#
/build
.classpath
.project
.settings
bin
build
target
language: java
install: mvn -U install --quiet -DskipTests=true
script: mvn clean test
# Spring Bootstrap
Experimental work based on discussions at SpringOne2GX 2012. See also the 'bootstrap' branch of Spring.
## Elevator Pitch
Opinionated view of the Spring family so that new users can quickly get to the 'meat and potatoes'. Assumes no knowledge of the Java development ecosystem. Absolutely no code generation and no XML.
## Installing
You need to build from source for now, but when it's done instructions will look like this:
1) Get Java
Download and install the Java SDK from www.java.com
2) Get Spring
`curl -s try.springsource.org | bash` or use the Windows installer
3) Get to Work!
spr run yoursourcefile.groovy
## What? It's Groovy then? or like Grails? or another Roo?
There is a command line tool that uses Groovy underneath so that we can present simple snippets that can just run:
@Controller
class ThisWillActuallyRun {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!"
}
}
By inspecting the code for well known annotations we can `@Grab` appropriate dependencies and also dynamically add `import` statements. Groovy makes this really easy.
If you don't want to use the command line tool, and you would rather work using Java and an IDE you can. Just add a `main()` method that calls `SpringApplication` and add `@EnableAutoConfiguration`:
import org.springframework.bootstrap.*;
import org.springframework.context.annotation.*;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
}
}
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!"
}
}
## Under the hood
There are a number of disparate parts of Bootstrap. Here are the important classes:
### The Spring CLI
The 'spr' command line application compiles and runs Groovy source, adding `import` statements and `@Grab` annotations. The application can also watch files, automatically recompiling and restarting when they change.
### SpringApplication
The `SpringApplication` class provides the main entry point for a standalone Spring Application. Its sole job is to create and refresh an appropriate Spring `ApplicationContext`. Any contained beans that implements `CommandLineRunner` will be executed after the context has started. A `SpringApplication` can load beans from a number of different sources, including classes, packages (scanned) or XML files. By default a `AnnotationConfigApplicationContext` or `AnnotationConfigEmbeddedWebApplicationContext` depending on your classpath.
### EmbeddedWebApplicationContext
The `EmbeddedWebApplicationContext` will probably be part of Spring 4.0. It provides a Spring 'WebApplicationContext' that can bootstrap itself and start and embedded servlet container. Support is provided for Tomcat and Jetty.
### @EnableAutoConfigure
The `@EnableAutoConfigure` can be used on a `@Configuration` class to trigger auto-configuration of the Spring context. Auto-configuration attempts to guess what beans a user might want based on their classpath. For example, If a 'HSQLDB' is on the classpath the user probably wants an in-memory database to be defined. Auto-configuration will back away as the user starts to define their own beans.
### @Conditional
The `@Conditional` annotation will probably be part of Spring 4.0. It provides allows `@Configuration` classes to be skipped depending on conditions. Bootstrap provides `@ConditionalOnBean`, `@ConditionalOnMissingBean` and `@ConditionalOnClass` annotations are used when defining auto-configuration classes.
## Building the code
Use maven to build the source code.
mvn clean install
## Importing into eclipse
You can use m2e or `maven eclipse:eclipse`.
Project specific settings are configured for source formatting. If you are using m2e please follow these steps to install eclipse support:
* Select `Install new software` from the `help` menu
* Click `Add...` to add a new repository
* Click the `Archive...` button
* Select `org.eclipse.m2e.maveneclipse.site-0.0.1-SNAPSHOT-site.zip` from the `eclipse` folder in this checkout
* Install "Maven Integration for the maven-eclipse-plugin"
If you prefer you can import settings manually from the `/eclipse` folder.
## Samples
The following samples are included. To run use `java -jar <archive>-full.jar`
* spring-bootstrap-simple-sample - A simple command line application
* spring-bootstrap-jetty-sample - Embedded Jetty
* spring-bootstrap-tomcat-sample - Embedded Tomcat
* spring-bootstrap-data-sample - Spring Data JPA + Hibernate + HSQLDB
This diff is collapsed.
This diff is collapsed.
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_Spring Java Conventions
formatter_settings_version=12
org.eclipse.jdt.ui.exception.name=e
org.eclipse.jdt.ui.gettersetter.use.is=false
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=java;javax;org;com;\#;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.keywordthis=false
org.eclipse.jdt.ui.ondemandthreshold=9999
org.eclipse.jdt.ui.overrideannotation=true
org.eclipse.jdt.ui.staticondemandthreshold=9999
org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/*\n * Copyright 2012-2013 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http\://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * @author ${user}\n *\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Javadoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="false" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\nthrow new UnsupportedOperationException("Auto-generated method stub");</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
sp_cleanup.add_missing_override_annotations_interface_methods=false
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=true
sp_cleanup.always_use_this_for_non_static_method_access=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=true
sp_cleanup.format_source_code_changes_only=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=false
sp_cleanup.make_type_abstract_if_missing_method=false
sp_cleanup.make_variable_declarations_final=true
sp_cleanup.never_use_blocks=false
sp_cleanup.never_use_parentheses_in_expressions=true
sp_cleanup.on_save_use_additional_actions=true
sp_cleanup.organize_imports=true
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
sp_cleanup.remove_trailing_whitespaces=false
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
sp_cleanup.remove_unnecessary_casts=true
sp_cleanup.remove_unnecessary_nls_tags=false
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
sp_cleanup.remove_unused_private_members=false
sp_cleanup.remove_unused_private_methods=true
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=true
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false
sp_cleanup.use_this_for_non_static_method_access=false
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-applications</artifactId>
<packaging>pom</packaging>
<properties>
<main.basedir>${project.basedir}/..</main.basedir>
</properties>
<modules>
<module>spring-bootstrap-application</module>
<module>spring-bootstrap-batch-application</module>
<module>spring-bootstrap-integration-application</module>
<module>spring-bootstrap-jpa-application</module>
<module>spring-bootstrap-web-application</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-application</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-batch-application</artifactId>
<packaging>pom</packaging>
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap-application</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-integration-application</artifactId>
<packaging>pom</packaging>
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap-application</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-jpa-application</artifactId>
<packaging>pom</packaging>
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap-application</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-web-application</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${project.basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap-application</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>spring-bootstrap-parent</artifactId>
<groupId>org.springframework.bootstrap</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-bootstrap-cli</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer>
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer>
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer />
<transformer>
<mainClass>${start-class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-distribution</id>
<phase>install</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<inherited>false</inherited>
<configuration>
<descriptors>
<descriptor>src/main/assembly/descriptor.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>objenesis</artifactId>
<groupId>org.objenesis</groupId>
</exclusion>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<start-class>org.springframework.bootstrap.cli.SpringBootstrapCli</start-class>
</properties>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-cli</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<start-class>org.springframework.bootstrap.cli.SpringBootstrapCli</start-class>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-bootstrap</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.springframework.bootstrap.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${start-class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<descriptors>
<descriptor>src/main/assembly/descriptor.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-distribution</id>
<phase>install</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
</project>
package org.test
@GrabResolver(name='spring-milestone', root='http://repo.springframework.org/milestone')
@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot')
@Grab("org.springframework.bootstrap:spring-bootstrap:0.0.1-SNAPSHOT")
@Grab("org.springframework:spring-context:4.0.0.BOOTSTRAP-SNAPSHOT")
@org.springframework.bootstrap.context.annotation.EnableAutoConfiguration
@org.springframework.stereotype.Component
class Example implements org.springframework.bootstrap.CommandLineRunner {
@org.springframework.beans.factory.annotation.Autowired
private MyService myService;
public void run(String... args) {
print "Hello " + this.myService.sayWorld();
}
}
@org.springframework.stereotype.Service
class MyService {
public String sayWorld() {
return "World!";
}
}
@org.springframework.bootstrap.context.annotation.EnableAutoConfiguration
@Controller
class Example {
@Autowired
private MyService myService;
@RequestMapping("/")
@ResponseBody
public String helloWorld() {
return myService.sayWorld();
}
}
@Service
class MyService {
public String sayWorld() {
return "World!";
}
}
<assembly>
<id>dist</id>
<formats>
<format>zip</format>
<format>dir</format>
</formats>
<baseDirectory>spring-${project.version}</baseDirectory>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/scripts</directory>
<outputDirectory>bin</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>bin</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
<filtered>true</filtered>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<includes>
<include>org.springframework.bootstrap:spring-bootstrap-cli:jar:*</include>
</includes>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.io.IOException;
import java.io.PrintStream;
/**
* Abstract {@link Command} implementation.
*
* @author Phillip Webb
*/
public abstract class AbstractCommand implements Command {
private String name;
private boolean optionCommand;
private String description;
/**
* Create a new {@link AbstractCommand} instance.
* @param name the name of the command
* @param description the command description
*/
public AbstractCommand(String name, String description) {
this(name, description, false);
}
/**
* Create a new {@link AbstractCommand} instance.
* @param name the name of the command
* @param description the command description
* @param optionCommand if this command is an option command (see
* {@link Command#isOptionCommand()}
*/
public AbstractCommand(String name, String description, boolean optionCommand) {
this.name = name;
this.description = description;
this.optionCommand = optionCommand;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public boolean isOptionCommand() {
return this.optionCommand;
}
@Override
public String getUsageHelp() {
return null;
}
@Override
public void printHelp(PrintStream out) throws IOException {
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
/**
* Runtime exception wrapper that defines additional {@link Option}s that are understood
* by the {@link SpringBootstrapCli}.
*
* @author Phillip Webb
*/
public class BootstrapCliException extends RuntimeException {
private static final long serialVersionUID = 0L;
private final EnumSet<Option> options;
/**
* Create a new {@link BootstrapCliException} with the specified options.
* @param options the exception options
*/
public BootstrapCliException(Option... options) {
this.options = asEnumSet(options);
}
/**
* Create a new {@link BootstrapCliException} with the specified options.
* @param message the exception message to display to the user
* @param options the exception options
*/
public BootstrapCliException(String message, Option... options) {
super(message);
this.options = asEnumSet(options);
}
/**
* Create a new {@link BootstrapCliException} with the specified options.
* @param message the exception message to display to the user
* @param cause the underlying cause
* @param options the exception options
*/
public BootstrapCliException(String message, Throwable cause, Option... options) {
super(message, cause);
this.options = asEnumSet(options);
}
private EnumSet<Option> asEnumSet(Option[] options) {
if (options == null || options.length == 0) {
return EnumSet.noneOf(Option.class);
}
return EnumSet.copyOf(Arrays.asList(options));
}
/**
* Returns options a set of options that are understood by the
* {@link SpringBootstrapCli}.
*/
public Set<Option> getOptions() {
return Collections.unmodifiableSet(this.options);
}
/**
* Specific options understood by the {@link SpringBootstrapCli}.
*/
public static enum Option {
/**
* Print basic CLI usage information.
*/
SHOW_USAGE,
/**
* Print the stack-trace of the exception.
*/
STACK_TRACE
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.io.IOException;
import java.io.PrintStream;
/**
* A single command that can be run from the CLI.
*
* @author Phillip Webb
* @see #run(String...)
*/
public interface Command {
/**
* Returns the name of the command.
*/
String getName();
/**
* Returns {@code true} if this is an 'option command'. An option command is a special
* type of command that usually makes more sense to present as if it is an option. For
* example '--help'.
*/
boolean isOptionCommand();
/**
* Returns a description of the command.
*/
String getDescription();
/**
* Returns usage help for the command. This should be a simple one-line string
* describing basic usage. eg. '[options] &lt;file&gt;'. Do not include the name of
* the command in this string.
*/
String getUsageHelp();
/**
* Prints help for the command.
* @param out the output writer to display help
* @throws IOException
*/
void printHelp(PrintStream out) throws IOException;
/**
* Run the command.
* @param args command arguments (this will not include the command itself)
* @throws Exception
*/
void run(String... args) throws Exception;
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import static java.util.Arrays.*;
/**
* {@link Command} to 'create' a new spring groovy script.
*
* @author Phillip Webb
*/
public class CreateCommand extends OptionParsingCommand {
public CreateCommand() {
super("create", "Create an new spring groovy script");
}
@Override
public String getUsageHelp() {
return "[options] <file>";
}
@Override
protected OptionParser createOptionParser() {
OptionParser parser = new OptionParser();
parser.acceptsAll(asList("overwite", "f"), "Overwrite any existing file");
parser.accepts("type", "Create a specific application type").withOptionalArg()
.ofType(String.class).describedAs("web, batch, integration");
return parser;
}
@Override
protected void run(OptionSet options) {
throw new IllegalStateException("Not implemented"); // FIXME
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
/**
* Exception thrown when no CLI options are specified.
*
* @author Phillip Webb
*/
class NoArgumentsException extends BootstrapCliException {
private static final long serialVersionUID = 1L;
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
/**
* Exception thrown when the 'help' command is issued without any arguments.
*
* @author Phillip Webb
*/
class NoHelpCommandArgumentsException extends BootstrapCliException {
private static final long serialVersionUID = 1L;
public NoHelpCommandArgumentsException() {
super(Option.SHOW_USAGE);
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
/**
* Exception thrown when an unknown command is specified.
*
* @author Phillip Webb
*/
class NoSuchCommandException extends BootstrapCliException {
private static final long serialVersionUID = 1L;
public NoSuchCommandException(String name) {
super(String.format("%1$s: '%2$s' is not a valid command. See '%1$s --help'.",
SpringBootstrapCli.CLI_APP, name));
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
/**
* Exception thrown when an unknown root option is specified. This only applies to
* {@link Command#isOptionCommand() option command}.
*
* @author Phillip Webb
*/
class NoSuchOptionException extends BootstrapCliException {
private static final long serialVersionUID = 1L;
public NoSuchOptionException(String name) {
super("Unknown option: --" + name, Option.SHOW_USAGE);
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.io.IOException;
import java.io.PrintStream;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
/**
* Base class for any {@link Command}s that use an {@link OptionParser}.
*
* @author Phillip Webb
*/
public abstract class OptionParsingCommand extends AbstractCommand {
private OptionParser parser;
public OptionParsingCommand(String name, String description) {
super(name, description);
this.parser = createOptionParser();
}
protected abstract OptionParser createOptionParser();
@Override
public void printHelp(PrintStream out) throws IOException {
this.parser.printHelpOn(out);
}
@Override
public final void run(String... args) throws Exception {
OptionSet options = parser.parse(args);
run(options);
}
protected abstract void run(OptionSet options) throws Exception;
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.awt.Desktop;
import java.io.File;
import java.util.List;
import java.util.logging.Level;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.bootstrap.cli.runner.BootstrapRunner;
import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration;
import static java.util.Arrays.asList;
/**
* {@link Command} to 'run' a spring groovy script.
*
* @author Phillip Webb
* @see BootstrapRunner
*/
public class RunCommand extends OptionParsingCommand {
private OptionSpec<Void> noWatchOption; // FIXME
private OptionSpec<Void> editOption;
private OptionSpec<Void> noGuessImportsOption;
private OptionSpec<Void> noGuessDependenciesOption;
private OptionSpec<Void> verboseOption;
private OptionSpec<Void> quiteOption;
public RunCommand() {
super("run", "Run a spring groovy script");
}
@Override
public String getUsageHelp() {
return "[options] <file>";
}
@Override
protected OptionParser createOptionParser() {
OptionParser parser = new OptionParser();
this.noWatchOption = parser.accepts("no-watch",
"Do not watch the specified file for changes");
this.editOption = parser.acceptsAll(asList("edit", "e"),
"Open the file with the default system editor");
this.noGuessImportsOption = parser.accepts("no-guess-imports",
"Do not attempt to guess imports");
this.noGuessDependenciesOption = parser.accepts("no-guess-dependencies",
"Do not attempt to guess dependencies");
this.verboseOption = parser.acceptsAll(asList("verbose", "v"), "Verbose logging");
this.quiteOption = parser.acceptsAll(asList("quiet", "q"), "Quiet logging");
return parser;
}
@Override
protected void run(OptionSet options) throws Exception {
List<String> nonOptionArguments = options.nonOptionArguments();
File file = getFileArgument(nonOptionArguments);
List<String> args = nonOptionArguments.subList(1, nonOptionArguments.size());
if (options.has(this.editOption)) {
Desktop.getDesktop().edit(file);
}
BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter(
options);
new BootstrapRunner(configuration, file, args.toArray(new String[args.size()]))
.compileAndRun();
}
private File getFileArgument(List<String> nonOptionArguments) {
if (nonOptionArguments.size() == 0) {
throw new RuntimeException("Please specify a file to run");
}
String filename = nonOptionArguments.get(0);
File file = new File(filename);
if (!file.isFile() || !file.canRead()) {
throw new RuntimeException("Unable to read '" + filename + "'");
}
return file;
}
/**
* Simple adapter class to present the {@link OptionSet} as a
* {@link BootstrapRunnerConfiguration}.
*/
private class BootstrapRunnerConfigurationAdapter implements
BootstrapRunnerConfiguration {
private OptionSet options;
public BootstrapRunnerConfigurationAdapter(OptionSet options) {
this.options = options;
}
@Override
public boolean isWatchForFileChanges() {
return !this.options.has(RunCommand.this.noWatchOption);
}
@Override
public boolean isGuessImports() {
return !this.options.has(RunCommand.this.noGuessImportsOption);
}
@Override
public boolean isGuessDependencies() {
return !this.options.has(RunCommand.this.noGuessDependenciesOption);
}
@Override
public Level getLogLevel() {
if (this.options.has(RunCommand.this.verboseOption)) {
return Level.FINEST;
}
if (this.options.has(RunCommand.this.quiteOption)) {
return Level.OFF;
}
return Level.INFO;
}
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/**
* Spring Bootstrap Command Line Interface. This is the main entry-point for the spring
* bootstrap command line application. This class will parse input arguments and delegate
* to a suitable {@link Command} implementation based on the first argument.
*
* <p>
* The '-d' and '--debug' switches are handled by this class, however, most argument
* parsing is left to the {@link Command} implementation. The {@link OptionParsingCommand}
* class provides a convenient base for command that need to parse arguments.
*
* @author Phillip Webb
* @see #main(String...)
* @see BootstrapCliException
* @see Command
* @see OptionParsingCommand
*/
public class SpringBootstrapCli {
public static final String CLI_APP = "spr";
private static final Set<BootstrapCliException.Option> NO_EXCEPTION_OPTIONS = EnumSet
.noneOf(BootstrapCliException.Option.class);
private List<Command> commands;
/**
* Create a new {@link SpringBootstrapCli} implementation with the default set of
* commands.
*/
public SpringBootstrapCli() {
setCommands(Arrays.asList(new VersionCommand(), new RunCommand(),
new CreateCommand()));
}
/**
* Set the command available to the CLI. Primarily used to support testing. NOTE: The
* 'help' command will be automatically provided in addition to this list.
* @param commands the commands to add
*/
protected void setCommands(List<? extends Command> commands) {
this.commands = new ArrayList<Command>(commands);
this.commands.add(0, new HelpCommand());
}
/**
* Run the CLI and handle and errors.
* @param args the input arguments
* @return a return status code (non zero is used to indicate an error)
*/
public int runAndHandleErrors(String... args) {
String[] argsWithoutDebugFlags = removeDebugFlags(args);
boolean debug = argsWithoutDebugFlags.length != args.length;
try {
run(argsWithoutDebugFlags);
return 0;
} catch (NoArgumentsException ex) {
showUsage();
return 1;
} catch (Exception ex) {
Set<BootstrapCliException.Option> options = NO_EXCEPTION_OPTIONS;
if (ex instanceof BootstrapCliException) {
options = ((BootstrapCliException) ex).getOptions();
}
errorMessage(ex.getMessage());
if (options.contains(BootstrapCliException.Option.SHOW_USAGE)) {
showUsage();
}
if (debug || options.contains(BootstrapCliException.Option.STACK_TRACE)) {
printStackTrace(ex);
}
return 1;
}
}
/**
* Parse the arguments and run a suitable command.
* @param args the arguments
* @throws Exception
*/
protected void run(String... args) throws Exception {
if (args.length == 0) {
throw new NoArgumentsException();
}
String commandName = args[0];
String[] commandArguments = Arrays.copyOfRange(args, 1, args.length);
find(commandName).run(commandArguments);
}
private Command find(String name) {
boolean isOption = name.startsWith("--");
if (isOption) {
name = name.substring(2);
}
for (Command candidate : this.commands) {
if ((isOption && candidate.isOptionCommand() || !isOption)
&& candidate.getName().equals(name)) {
return candidate;
}
}
throw (isOption ? new NoSuchOptionException(name) : new NoSuchCommandException(
name));
}
protected void showUsage() {
System.out.print("usage: " + CLI_APP + " ");
for (Command command : this.commands) {
if (command.isOptionCommand()) {
System.out.print("[--" + command.getName() + "] ");
}
}
System.out.println("");
System.out.println(" <command> [<args>]");
System.out.println("");
System.out.println("Available commands are:");
for (Command command : this.commands) {
if (!command.isOptionCommand()) {
System.out.println(String.format(" %1$-15s %2$s", command.getName(),
command.getDescription()));
}
}
System.out.println("");
System.out
.println("See 'spr help <command>' for more information on a specific command.");
}
protected void errorMessage(String message) {
System.err.println(message == null ? "Unexpected error" : message);
}
protected void printStackTrace(Exception ex) {
System.err.println("");
ex.printStackTrace(System.err);
System.err.println("");
}
private String[] removeDebugFlags(String[] args) {
List<String> rtn = new ArrayList<String>(args.length);
for (String arg : args) {
if (!("-d".equals(arg) || "--debug".equals(arg))) {
rtn.add(arg);
}
}
return rtn.toArray(new String[rtn.size()]);
}
/**
* Internal {@link Command} used for 'help' and '--help' requests.
*/
private class HelpCommand extends AbstractCommand {
public HelpCommand() {
super("help", "Show command help", true);
}
@Override
public void run(String... args) throws Exception {
if (args.length == 0) {
throw new NoHelpCommandArgumentsException();
}
String commandName = args[0];
for (Command command : SpringBootstrapCli.this.commands) {
if (!command.isOptionCommand() && command.getName().equals(commandName)) {
System.out.println(CLI_APP + " " + command.getName() + " - "
+ command.getDescription());
System.out.println();
if (command.getUsageHelp() != null) {
System.out.println("usage: " + CLI_APP + " " + command.getName()
+ " " + command.getUsageHelp());
System.out.println();
}
command.printHelp(System.out);
return;
}
}
throw new NoSuchCommandException(commandName);
}
}
/**
* The main CLI entry-point.
* @param args CLI arguments
*/
public static void main(String... args) {
int exitCode = new SpringBootstrapCli().runAndHandleErrors(args);
if (exitCode != 0) {
System.exit(exitCode);
}
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli;
/**
* {@link Command} to displat the 'version' number.
*
* @author Phillip Webb
*/
public class VersionCommand extends AbstractCommand {
public VersionCommand() {
super("version", "Show the version", true);
}
@Override
public void run(String... args) {
throw new IllegalStateException("Not implemented"); // FIXME
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
/**
* General purpose AST utilities.
*
* @author Phillip Webb
*/
public abstract class AstUtils {
/**
* Determine if an {@link AnnotatedNode} has one or more of the specified annotations.
*/
public static boolean hasLeastOneAnnotation(AnnotatedNode node, String... annotations) {
for (AnnotationNode annotationNode : node.getAnnotations()) {
for (String annotation : annotations) {
if (annotation.equals(annotationNode.getClassNode().getName())) {
return true;
}
}
}
return false;
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
/**
* Strategy that can be used to apply some auto-configuration during the
* {@link CompilePhase#CONVERSION} Groovy compile phase.
*
* @author Phillip Webb
*/
public abstract class CompilerAutoConfiguration {
/**
* Strategy method used to determine when compiler auto-configuration should be
* applied. Defaults to always.
* @param classNode the class node
* @return {@code true} if the compiler should be auto configured using this class. If
* this method returns {@code false} no other strategy methods will be called.
*/
public boolean matches(ClassNode classNode) {
return true;
}
/**
* Apply any dependency customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param dependencies dependency customizer
* @throws CompilationFailedException
*/
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
}
/**
* Apply any import customizations. This method will only be called if
* {@link #matches} returns {@code true}.
* @param imports import customizer
* @throws CompilationFailedException
*/
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
}
/**
* Apply any customizations to the main class. This method will only be called if
* {@link #matches} returns {@code true}. This method is useful when a groovy file
* defines more than one class but customization only applies to the first class.
*/
public void applyToMainClass(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
}
/**
* Apply any additional configuration.
*/
public void apply(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
import groovy.grape.Grape;
import groovy.lang.Grapes;
import groovy.lang.GroovyClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Customizer that allows dependencies to be added during compilation. Delegates to Groovy
* {@link Grapes} to actually resolve dependencies. This class provides a fluent API for
* conditionally adding dependencies. For example:
* {@code dependencies.ifMissing("com.corp.SomeClass").add(group, module, version)}.
*
* @author Phillip Webb
*/
public class DependencyCustomizer {
private final GroovyClassLoader loader;
private final List<Map<String, Object>> dependencies;
/**
* Create a new {@link DependencyCustomizer} instance. The {@link #call()} method must
* be used to actually resolve dependencies.
* @param loader
*/
public DependencyCustomizer(GroovyClassLoader loader) {
this.loader = loader;
this.dependencies = new ArrayList<Map<String, Object>>();
}
/**
* Create a new nested {@link DependencyCustomizer}.
* @param parent
*/
protected DependencyCustomizer(DependencyCustomizer parent) {
this.loader = parent.loader;
this.dependencies = parent.dependencies;
}
/**
* Create a nested {@link DependencyCustomizer} that only applies if the specified
* class names are not on the class path.
* @param classNames the class names to test
* @return a nested {@link DependencyCustomizer}
*/
public DependencyCustomizer ifMissingClasses(final String... classNames) {
return new DependencyCustomizer(this) {
@Override
protected boolean canAdd() {
for (String classname : classNames) {
try {
DependencyCustomizer.this.loader.loadClass(classname);
return false;
} catch (Exception e) {
}
}
return DependencyCustomizer.this.canAdd();
}
};
}
/**
* Add a single dependencies.
* @param group the group ID
* @param module the module ID
* @param version the version
* @return this {@link DependencyCustomizer} for continued use
*/
@SuppressWarnings("unchecked")
public DependencyCustomizer add(String group, String module, String version) {
if (canAdd()) {
Map<String, Object> dependency = new HashMap<String, Object>();
dependency.put("group", group);
dependency.put("module", module);
dependency.put("version", version);
dependency.put("transitive", true);
return add(dependency);
}
return this;
}
/**
* Add a dependencies.
* @param dependencies a map of the dependencies to add.
* @return this {@link DependencyCustomizer} for continued use
*/
public DependencyCustomizer add(Map<String, Object>... dependencies) {
this.dependencies.addAll(Arrays.asList(dependencies));
return this;
}
/**
* Strategy called to test if dependencies can be added. Subclasses override as
* requred.
*/
protected boolean canAdd() {
return true;
}
/**
* Apply the dependencies.
*/
void call() {
HashMap<String, Object> args = new HashMap<String, Object>();
args.put("classLoader", this.loader);
Grape.grab(args, this.dependencies.toArray(new Map[this.dependencies.size()]));
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
import groovy.lang.GroovyClassLoader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
/**
* Extension of the {@link GroovyClassLoader} that support for obtaining '.class' files as
* resources.
*
* @author Phillip Webb
*/
class ExtendedGroovyClassLoader extends GroovyClassLoader {
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
super(loader, config);
}
@Override
public InputStream getResourceAsStream(String name) {
InputStream resourceStream = super.getResourceAsStream(name);
if (resourceStream == null) {
byte[] bytes = this.classResources.get(name);
resourceStream = bytes == null ? null : new ByteArrayInputStream(bytes);
}
return resourceStream;
}
@Override
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = AccessController
.doPrivileged(new PrivilegedAction<InnerLoader>() {
@Override
public InnerLoader run() {
return new InnerLoader(ExtendedGroovyClassLoader.this);
}
});
return new ExtendedClassCollector(loader, unit, su);
}
/**
* Inner collector class used to track as classes are added.
*/
protected class ExtendedClassCollector extends ClassCollector {
protected ExtendedClassCollector(InnerLoader loader, CompilationUnit unit,
SourceUnit su) {
super(loader, unit, su);
}
@Override
protected Class<?> createClass(byte[] code, ClassNode classNode) {
Class<?> createdClass = super.createClass(code, classNode);
ExtendedGroovyClassLoader.this.classResources.put(classNode.getName()
.replace(".", "/") + ".class", code);
return createdClass;
}
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
import groovy.lang.GroovyClassLoader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.bootstrap.cli.compiler.autoconfigure.SpringBootstrapCompilerAutoConfiguration;
import org.springframework.bootstrap.cli.compiler.autoconfigure.SpringMvcCompilerAutoConfiguration;
/**
* Compiler for Groovy source files. Primarily a simple Facade for
* {@link GroovyClassLoader#parseClass(File)} with the following additional features:
* <ul>
* <li>{@link CompilerAutoConfiguration} strategies will de applied during compilation</li>
*
* <li>Multiple classes can be returned if the Groovy source defines more than one Class</li>
*
* <li>Generated class files can also be loaded using
* {@link ClassLoader#getResource(String)}</li>
* <ul>
*
* @author Phillip Webb
*/
public class GroovyCompiler {
// FIXME could be a strategy
private static final CompilerAutoConfiguration[] COMPILER_AUTO_CONFIGURATIONS = {
new SpringBootstrapCompilerAutoConfiguration(),
new SpringMvcCompilerAutoConfiguration(),
new SpringBootstrapCompilerAutoConfiguration() };
private GroovyCompilerConfiguration configuration;
private ExtendedGroovyClassLoader loader;
/**
* Create a new {@link GroovyCompiler} instance.
* @param configuration the compiler configuration
*/
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(),
compilerConfiguration);
compilerConfiguration
.addCompilationCustomizers(new CompilerAutoConfigureCustomizer());
}
/**
* Compile the specified Groovy source files, applying any
* {@link CompilerAutoConfiguration}s. All classes defined in the file will be
* returned from this method with the first item being the primary class (defined at
* the top of the file).
* @param file the file to compile
* @return compiled classes
* @throws CompilationFailedException
* @throws IOException
*/
public Class<?>[] compile(File file) throws CompilationFailedException, IOException {
this.loader.clearCache();
List<Class<?>> classes = new ArrayList<Class<?>>();
Class<?> mainClass = this.loader.parseClass(file);
for (Class<?> loadedClass : this.loader.getLoadedClasses()) {
classes.add(loadedClass);
}
classes.remove(mainClass);
classes.add(0, mainClass);
return classes.toArray(new Class<?>[classes.size()]);
}
/**
* {@link CompilationCustomizer} to call {@link CompilerAutoConfiguration}s.
*/
private class CompilerAutoConfigureCustomizer extends CompilationCustomizer {
public CompilerAutoConfigureCustomizer() {
super(CompilePhase.CONVERSION);
}
@Override
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode)
throws CompilationFailedException {
ImportCustomizer importCustomizer = new ImportCustomizer();
// Early sweep to get dependencies
DependencyCustomizer dependencyCustomizer = new DependencyCustomizer(
GroovyCompiler.this.loader);
for (CompilerAutoConfiguration autoConfiguration : COMPILER_AUTO_CONFIGURATIONS) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration.isGuessDependencies()) {
autoConfiguration.applyDependencies(dependencyCustomizer);
}
}
}
dependencyCustomizer.call();
// Additional auto configuration
for (CompilerAutoConfiguration autoConfiguration : COMPILER_AUTO_CONFIGURATIONS) {
if (autoConfiguration.matches(classNode)) {
if (GroovyCompiler.this.configuration.isGuessImports()) {
autoConfiguration.applyImports(importCustomizer);
importCustomizer.call(source, context, classNode);
}
if (source.getAST().getClasses().size() > 0
&& classNode.equals(source.getAST().getClasses().get(0))) {
autoConfiguration.applyToMainClass(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source,
classNode);
}
autoConfiguration
.apply(GroovyCompiler.this.loader,
GroovyCompiler.this.configuration, context, source,
classNode);
}
}
importCustomizer.call(source, context, classNode);
}
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler;
/**
* Configuration for the {@link GroovyCompiler}.
*
* @author Phillip Webb
*/
public interface GroovyCompilerConfiguration {
/**
* Returns if import declarations should be guessed.
*/
boolean isGuessImports();
/**
* Returns if jar dependencies should be guessed.
*/
boolean isGuessDependencies();
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.bootstrap.cli.compiler.AstUtils;
import org.springframework.bootstrap.cli.compiler.CompilerAutoConfiguration;
import org.springframework.bootstrap.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring Batch.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasLeastOneAnnotation(classNode, "EnableBatchProcessing");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifMissingClasses("org.springframework.batch.core.Job").add(
"org.springframework.batch", "spring-batch-core", "2.1.9.RELEASE");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports(
"org.springframework.batch.repeat.RepeatStatus",
"org.springframework.batch.core.scope.context.ChunkContext",
"org.springframework.batch.core.step.tasklet.Tasklet",
"org.springframework.batch.core.configuration.annotation.StepScope",
"org.springframework.batch.core.configuration.annotation.JobBuilderFactory",
"org.springframework.batch.core.configuration.annotation.StepBuilderFactory",
"org.springframework.batch.core.configuration.annotation.EnableBatchProcessing",
"org.springframework.batch.core.Step",
"org.springframework.batch.core.StepExecution",
"org.springframework.batch.core.StepContribution",
"org.springframework.batch.core.Job",
"org.springframework.batch.core.JobExecution",
"org.springframework.batch.core.JobParameter",
"org.springframework.batch.core.JobParameters",
"org.springframework.batch.core.launch.JobLauncher",
"org.springframework.batch.core.converter.DefaultJobParametersConverter");
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler.autoconfigure;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.bootstrap.cli.compiler.CompilerAutoConfiguration;
import org.springframework.bootstrap.cli.compiler.DependencyCustomizer;
import org.springframework.bootstrap.cli.compiler.GroovyCompilerConfiguration;
/**
* {@link CompilerAutoConfiguration} for Spring Bootstrap.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringBootstrapCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifMissingClasses("org.springframework.bootstrap.SpringApplication")
.add("org.springframework.bootstrap", "spring-bootstrap-application",
"0.0.1-SNAPSHOT");
// FIXME get the version
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addImports("javax.sql.DataSource",
"org.springframework.beans.factory.annotation.Autowired",
"org.springframework.beans.factory.annotation.Value",
"org.springframework.context.annotation.Import",
"org.springframework.context.annotation.ImportResource",
"org.springframework.context.annotation.Profile",
"org.springframework.context.annotation.Scope",
"org.springframework.context.annotation.Configuration",
"org.springframework.context.annotation.Bean",
"org.springframework.bootstrap.context.annotation.EnableAutoConfiguration");
imports.addStarImports("org.springframework.stereotype");
}
@Override
public void applyToMainClass(GroovyClassLoader loader,
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
if (true) {
addEnableAutoConfigurationAnnotation(source, classNode);
}
}
private void addEnableAutoConfigurationAnnotation(SourceUnit source,
ClassNode classNode) {
if (!hasEnableAutoConfigureAnnotation(classNode)) {
try {
Class<?> annotationClass = source
.getClassLoader()
.loadClass(
"org.springframework.bootstrap.context.annotation.EnableAutoConfiguration");
AnnotationNode annotationNode = new AnnotationNode(new ClassNode(
annotationClass));
classNode.addAnnotation(annotationNode);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
private boolean hasEnableAutoConfigureAnnotation(ClassNode classNode) {
for (AnnotationNode node : classNode.getAnnotations()) {
if ("EnableAutoConfiguration".equals(node.getClassNode()
.getNameWithoutPackage())) {
return true;
}
}
return false;
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.compiler.autoconfigure;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.springframework.bootstrap.cli.compiler.AstUtils;
import org.springframework.bootstrap.cli.compiler.CompilerAutoConfiguration;
import org.springframework.bootstrap.cli.compiler.DependencyCustomizer;
/**
* {@link CompilerAutoConfiguration} for Spring MVC.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguration {
@Override
public void applyDependencies(DependencyCustomizer dependencies) {
dependencies.ifMissingClasses("org.springframework.web.servlet.mvc.Controller")
.add("org.springframework", "spring-webmvc", "4.0.0.BOOTSTRAP-SNAPSHOT");
dependencies.ifMissingClasses("org.apache.catalina.startup.Tomcat",
"org.eclipse.jetty.server.Server").add("org.eclipse.jetty",
"jetty-webapp", "8.1.10.v20130312");
// FIXME restore Tomcat when we can get reload to work
// dependencies.ifMissingClasses("org.apache.catalina.startup.Tomcat",
// "org.eclipse.jetty.server.Server")
// .add("org.apache.tomcat.embed", "tomcat-embed-core", "7.0.37")
// .add("org.apache.tomcat.embed", "tomcat-embed-logging-juli", "7.0.37");
}
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasLeastOneAnnotation(classNode, "Controller", "EnableWebMvc");
}
@Override
public void applyImports(ImportCustomizer imports) {
imports.addStarImports("org.springframework.web.bind.annotation",
"org.springframework.web.servlet.config.annotation");
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.runner;
import java.io.File;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.springframework.bootstrap.cli.compiler.GroovyCompiler;
/**
* Compiles Groovy code running the resulting classes using a {@code SpringApplication}.
* Takes care of threading and class-loading issues and can optionally monitor files for
* changes.
*
* @author Phillip Webb
*/
public class BootstrapRunner {
// FIXME logging
private BootstrapRunnerConfiguration configuration;
private final File file;
private final String[] args;
private final GroovyCompiler compiler;
private RunThread runThread;
private FileWatchThread fileWatchThread;
/**
* Create a new {@link BootstrapRunner} instance.
* @param configuration the configuration
* @param file the file to compile/watch
* @param args input arguments
*/
public BootstrapRunner(final BootstrapRunnerConfiguration configuration, File file,
String... args) {
this.configuration = configuration;
this.file = file;
this.args = args;
this.compiler = new GroovyCompiler(configuration);
if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
System.setProperty("groovy.grape.report.downloads", "true");
}
}
/**
* Compile and run the application. This method is synchronized as it can be called by
* file monitoring threads.
* @throws Exception
*/
public synchronized void compileAndRun() throws Exception {
try {
// Shutdown gracefully any running container
if (this.runThread != null) {
this.runThread.shutdown();
this.runThread = null;
}
// Compile
Class<?>[] classes = this.compiler.compile(this.file);
if (classes.length == 0) {
throw new RuntimeException("No classes found in '" + this.file + "'");
}
// Run in new thread to ensure that the context classloader is setup
this.runThread = new RunThread(classes);
this.runThread.start();
this.runThread.join();
// Start monitoring for changes
if (this.fileWatchThread == null
&& this.configuration.isWatchForFileChanges()) {
this.fileWatchThread = new FileWatchThread();
this.fileWatchThread.start();
}
} catch (Exception ex) {
if (this.fileWatchThread == null) {
throw ex;
} else {
ex.printStackTrace();
}
}
}
/**
* Thread used to launch the Spring Application with the correct context classloader.
*/
private class RunThread extends Thread {
private final Class<?>[] classes;
private Object applicationContext;
/**
* Create a new {@link RunThread} instance.
* @param classes the classes to launch
*/
public RunThread(Class<?>... classes) {
this.classes = classes;
if (classes.length != 0) {
setContextClassLoader(classes[0].getClassLoader());
}
}
@Override
public void run() {
try {
// User reflection to load and call Spring
Class<?> application = getContextClassLoader().loadClass(
"org.springframework.bootstrap.SpringApplication");
Method method = application.getMethod("run", Object[].class,
String[].class);
this.applicationContext = method.invoke(null, this.classes,
BootstrapRunner.this.args);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Shutdown the thread, closing any previously opened appplication context.
*/
public synchronized void shutdown() {
if (this.applicationContext != null) {
try {
Method method = this.applicationContext.getClass().getMethod("close");
method.invoke(this.applicationContext);
} catch (NoSuchMethodException ex) {
// Not an application context that we can close
} catch (Exception ex) {
ex.printStackTrace();
} finally {
this.applicationContext = null;
}
}
}
}
/**
* Thread to watch for file changes and trigger recompile/reload.
*/
private class FileWatchThread extends Thread {
private long previous;
public FileWatchThread() {
this.previous = BootstrapRunner.this.file.lastModified();
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
long current = BootstrapRunner.this.file.lastModified();
if (this.previous < current) {
this.previous = current;
compileAndRun();
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (Exception ex) {
// Swallow, will be reported by compileAndRun
}
}
}
}
}
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.cli.runner;
import java.util.logging.Level;
import org.springframework.bootstrap.cli.compiler.GroovyCompilerConfiguration;
/**
* Configuration for the {@link BootstrapRunner}.
*
* @author Phillip Webb
*/
public interface BootstrapRunnerConfiguration extends GroovyCompilerConfiguration {
/**
* Returns {@code true} if the source file should be monitored for changes and
* automatically recompiled.
*/
boolean isWatchForFileChanges();
/**
* Returns the logging level to use.
*/
Level getLogLevel();
}
package org.springframework.bootstrap.cli;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringBootstrapCli}.
*
* @author Phillip Webb
*/
public class SpringBootstrapCliTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private SpringBootstrapCli cli;
@Mock
private Command regularCommand;
@Mock
private Command optionCommand;
private Set<Call> calls = EnumSet.noneOf(Call.class);
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.cli = new SpringBootstrapCli() {
@Override
protected void showUsage() {
SpringBootstrapCliTests.this.calls.add(Call.SHOW_USAGE);
super.showUsage();
};
@Override
protected void errorMessage(String message) {
SpringBootstrapCliTests.this.calls.add(Call.ERROR_MESSAGE);
super.errorMessage(message);
}
@Override
protected void printStackTrace(Exception ex) {
SpringBootstrapCliTests.this.calls.add(Call.PRINT_STACK_TRACE);
super.printStackTrace(ex);
}
};
given(this.regularCommand.getName()).willReturn("command");
given(this.regularCommand.getDescription()).willReturn("A regular command");
given(this.optionCommand.getName()).willReturn("option");
given(this.optionCommand.getDescription()).willReturn("An optional command");
given(this.optionCommand.isOptionCommand()).willReturn(true);
this.cli.setCommands(Arrays.asList(this.regularCommand, this.optionCommand));
}
@Test
public void runWithoutArguments() throws Exception {
this.thrown.expect(NoArgumentsException.class);
this.cli.run();
}
@Test
public void runCommand() throws Exception {
this.cli.run("command", "--arg1", "arg2");
verify(this.regularCommand).run("--arg1", "arg2");
}
@Test
public void runOptionCommand() throws Exception {
this.cli.run("--option", "--arg1", "arg2");
verify(this.optionCommand).run("--arg1", "arg2");
}
@Test
public void runOptionCommandWithoutOption() throws Exception {
this.cli.run("option", "--arg1", "arg2");
verify(this.optionCommand).run("--arg1", "arg2");
}
@Test
public void runOptionOnNonOptionCommand() throws Exception {
this.thrown.expect(NoSuchOptionException.class);
this.cli.run("--command", "--arg1", "arg2");
}
@Test
public void missingCommand() throws Exception {
this.thrown.expect(NoSuchCommandException.class);
this.cli.run("missing");
}
@Test
public void handlesSuccess() throws Exception {
int status = this.cli.runAndHandleErrors("--option");
assertThat(status, equalTo(0));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.noneOf(Call.class)));
}
@Test
public void handlesNoArgumentsException() throws Exception {
int status = this.cli.runAndHandleErrors();
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.of(Call.SHOW_USAGE)));
}
@Test
public void handlesNoSuchOptionException() throws Exception {
int status = this.cli.runAndHandleErrors("--missing");
assertThat(status, equalTo(1));
assertThat(this.calls,
equalTo((Set<Call>) EnumSet.of(Call.ERROR_MESSAGE, Call.SHOW_USAGE)));
}
@Test
public void handlesRegularException() throws Exception {
willThrow(new RuntimeException()).given(this.regularCommand).run();
int status = this.cli.runAndHandleErrors("command");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.of(Call.ERROR_MESSAGE)));
}
@Test
public void handlesExceptionWithDashD() throws Exception {
willThrow(new RuntimeException()).given(this.regularCommand).run();
int status = this.cli.runAndHandleErrors("command", "-d");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.of(Call.ERROR_MESSAGE,
Call.PRINT_STACK_TRACE)));
}
@Test
public void handlesExceptionWithDashDashDebug() throws Exception {
willThrow(new RuntimeException()).given(this.regularCommand).run();
int status = this.cli.runAndHandleErrors("command", "--debug");
assertThat(status, equalTo(1));
assertThat(this.calls, equalTo((Set<Call>) EnumSet.of(Call.ERROR_MESSAGE,
Call.PRINT_STACK_TRACE)));
}
@Test
public void exceptionMessages() throws Exception {
assertThat(new NoSuchOptionException("name").getMessage(),
equalTo("Unknown option: --name"));
assertThat(new NoSuchCommandException("name").getMessage(),
equalTo("spr: 'name' is not a valid command. See 'spr --help'."));
}
@Test
public void help() throws Exception {
this.cli.run("help", "command");
verify(this.regularCommand).printHelp((PrintStream) anyObject());
}
@Test
public void helpNoCommand() throws Exception {
this.thrown.expect(NoHelpCommandArgumentsException.class);
this.cli.run("help");
}
@Test
public void helpUnknownCommand() throws Exception {
this.thrown.expect(NoSuchCommandException.class);
this.cli.run("help", "missing");
}
private static enum Call {
SHOW_USAGE, ERROR_MESSAGE, PRINT_STACK_TRACE
}
}
# Spring Bootstrap Groovy
Spring Bootstrap Groovy gives you the quickest possible getting
started experience with Spring apps, whether you are writing a web
app, a batch job or a standalone java app.
## Building and Testing
To avoid problems with classpaths and existing JVM-based build tools,
Spring Bootstrap Groovy uses an exec plugin call to launch `groovyc`.
You need to have a `sh` on your path along with `groovyc` (2.1.x),
`find` and `xargs`. These tools are standard on a Mac or Linux
distribution, and available using Cygwin on Windows. Once it is
built, the zip file is portable.
Here are the steps to build and test:
$ mvn install
The `spring` executable is then available at
`spring-bootstrap-groovy/target/spring-<VERSION>`. There is also a jar
file with the Groovy Bootstrap components. The `spring` executable
includes jars from `SPRING_HOME` in the classpath so you can run it
while you are developing like this
$ export SPRING_HOME=<spring-bootstrap-groovy>/target
$ <spring-bootstrap-groovy>/src/main/scripts/spring App.groovy
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-groovy</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${dependency.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.ivy</groupId>
<artifactId>ivy</artifactId>
<version>2.2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>grapes</id>
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.5</version>
<configuration>
<filesets>
<fileset>
<directory>${user.home}/.groovy/grapes</directory>
<includes>
<include>org.springframework*/**</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>src/main/groovy</directory>
<filtering>true</filtering>
<targetPath>../generated-sources/groovy</targetPath>
</resource>
</resources>
<plugins>
<plugin>
<!-- Using exec plugin to compile groovy because it needs to be forked
cleanly. Only works if sh, find, xargs and groovyc are on PATH -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>compile-plugin</id>
<phase>compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>sh</executable>
<arguments>
<argument>-c</argument>
<argument>find target/generated-sources/groovy -name *.groovy |
xargs groovyc -d target/classes</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<descriptors>
<descriptor>src/main/assembly/descriptor.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-distribution</id>
<phase>install</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.7.0-01</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<versionRange>[3.0,)</versionRange>
<goals>
<goal>testCompile</goal>
<goal>compile</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<delimiters>
<delimiter>@@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
<assembly>
<id>dist</id>
<formats>
<format>zip</format>
<format>dir</format>
</formats>
<baseDirectory>spring-${project.version}</baseDirectory>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>src/main/scripts</directory>
<outputDirectory>bin</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
</fileSet>
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>bin</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
<filtered>true</filtered>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<includes>
<include>org.springframework.bootstrap:spring-bootstrap-groovy:jar:*</include>
</includes>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
package org.springframework.bootstrap.grapes
@GrabResolver(name='spring-milestone', root='http://repo.springframework.org/milestone')
@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot')
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework.bootstrap:spring-bootstrap:@@version@@")
@Grab("org.springframework.batch:spring-batch-core:2.2.0.M1")
@Grab("org.springframework:spring-context:@@dependency.springframework.version@@")
class BatchGrapes {
}
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean
import org.springframework.bootstrap.CommandLineRunner
import org.springframework.batch.core.Job
import org.springframework.batch.core.converter.DefaultJobParametersConverter
import org.springframework.batch.core.converter.JobParametersConverter
import org.springframework.batch.core.launch.JobLauncher
import org.springframework.context.annotation.Configuration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.util.StringUtils
import groovy.util.logging.Log
@Configuration
@ConditionalOnMissingBean(CommandLineRunner)
@Log
class BatchCommand {
@Autowired(required=false)
private JobParametersConverter converter = new DefaultJobParametersConverter()
@Autowired
private JobLauncher jobLauncher
@Autowired
private Job job
@Bean
CommandLineRunner batchCommandLineRunner() {
return new CommandLineRunner() {
void run(String... args) {
log.info("Running default command line with: ${args}")
launchJobFromProperties(StringUtils.splitArrayElementsIntoProperties(args, "="))
}
}
}
protected void launchJobFromProperties(Properties properties) {
jobLauncher.run(job, converter.getJobParameters(properties))
}
}
package org.springframework.bootstrap.grapes
@GrabResolver(name='spring-milestone', root='http://repo.springframework.org/milestone')
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework:spring-jdbc:4.0.0.BOOTSTRAP-SNAPSHOT")
@Grab("org.springframework.batch:spring-batch-core:2.2.0.M1")
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
import javax.annotation.PostConstruct
import javax.sql.DataSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import org.springframework.core.io.ResourceLoader
@Configuration // TODO: make this conditional
class BatchInitializationGrapes {
@Autowired
private DataSource dataSource
@Autowired
private Environment environment
@Autowired
private ResourceLoader resourceLoader
@PostConstruct
protected void initialize() {
String platform = org.springframework.batch.support.DatabaseType.fromMetaData(dataSource).toString().toLowerCase()
if (platform=="hsql") {
platform = "hsqldb"
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator()
populator.addScript(resourceLoader.getResource("org/springframework/batch/core/schema-${platform}.sql"))
populator.setContinueOnError(true)
DatabasePopulatorUtils.execute(populator, dataSource)
}
}
\ No newline at end of file
package org.springframework.bootstrap.grapes
// Spring stuff needs to be on the system classloader apparently (when using @Configuration)
@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot')
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework:spring-context:@@dependency.springframework.version@@")
@Grab("org.springframework.bootstrap:spring-bootstrap:@@version@@")
@GrabExclude("commons-logging:commons-logging")
@Grab("org.slf4j:jcl-over-slf4j:1.6.1")
@Grab("org.slf4j:slf4j-jdk14:1.6.1")
class BootstrapGrapes {
}
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration
import org.springframework.context.annotation.Configuration
@Configuration
// @EnableAutoConfiguration
class BootstrapAutoConfiguration {
}
\ No newline at end of file
package org.springframework.bootstrap.grapes
import org.springframework.core.type.StandardAnnotationMetadata
import org.springframework.util.ClassUtils
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory
import groovy.util.logging.Log
@GrabResolver(name='spring-snapshot', root='http://repo.springframework.org/snapshot')
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework:spring-core:4.0.0.BOOTSTRAP-SNAPSHOT")
@GrabExclude("commons-logging:commons-logging")
@Grab("org.slf4j:jcl-over-slf4j:1.6.1")
@Grab("org.slf4j:slf4j-jdk14:1.6.1")
@Log
class Dependencies {
static List<String> defaults() {
return ["org.springframework.bootstrap.grapes.BootstrapGrapes"]
}
static List<String> dependencies(Collection<String> configs) {
def result = []
if (isWeb(configs)) {
log.info("Adding web dependencies.")
result.addAll(web())
}
if (isBatch(configs)) {
log.info("Adding batch dependencies.")
result.addAll(batch())
result << "org.springframework.bootstrap.grapes.BatchCommand"
result << "org.springframework.bootstrap.grapes.BatchInitializationGrapes"
}
if (isHadoop(configs)) {
log.info("Adding info dependencies.")
result.addAll(hadoop())
result << "org.springframework.bootstrap.grapes.HadoopContext"
}
return result
}
static String[] web() {
def result = []
result << "org.springframework.bootstrap.grapes.WebGrapes"
if (!isEmbeddedServerAvailable()) { result << "org.springframework.bootstrap.grapes.TomcatGrapes" }
return result
}
static String[] batch() {
def result = []
result << "org.springframework.bootstrap.grapes.BatchGrapes"
return result
}
static String[] hadoop() {
def result = []
result << "org.springframework.bootstrap.grapes.HadoopGrapes"
return result
}
static boolean isWeb(Collection<String> configs) {
SimpleMetadataReaderFactory factory = new SimpleMetadataReaderFactory()
return configs.any { config ->
def meta = factory.getMetadataReader(config).getAnnotationMetadata()
meta.hasAnnotation("org.springframework.stereotype.Controller") || meta.hasAnnotation("org.springframework.web.servlet.config.annotation.EnableWebMvc")
}
}
static boolean isHadoop(Collection<String> configs) {
SimpleMetadataReaderFactory factory = new SimpleMetadataReaderFactory()
return configs.any { config ->
config.contains("Hadoop")
}
}
static boolean isBatch(Collection<String> configs) {
SimpleMetadataReaderFactory factory = new SimpleMetadataReaderFactory()
return configs.any { config ->
def meta = factory.getMetadataReader(config).getAnnotationMetadata()
meta.hasAnnotation("org.springframework.batch.core.configuration.annotation.EnableBatchProcessing")
}
}
static boolean isEmbeddedServerAvailable() {
return ClassUtils.isPresent("org.apache.catalina.startup.Tomcat") || ClassUtils.isPresent("org.mortbay.jetty.Server")
}
}
\ No newline at end of file
package org.springframework.bootstrap.grapes
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework.data:spring-data-hadoop:1.0.0.RELEASE")
@Grab("org.springframework.bootstrap:spring-bootstrap:@@version@@")
@Grab("org.springframework:spring-context:4.0.0.BOOTSTRAP-SNAPSHOT")
@Grab("org.apache.hadoop:hadoop-examples:1.0.4")
@GrabExclude("org.mortbay.jetty:sevlet-api-2.5")
@GrabExclude("org.mortbay.jetty:jetty")
@GrabExclude("org.mortbay.jetty:jetty-util")
@GrabExclude("org.mortbay.jetty:jsp-2.1")
@GrabExclude("org.mortbay.jetty:jsp-api-2.1")
@GrabExclude("tomcat:jasper-runtime")
@GrabExclude("tomcat:jasper-compiler")
class HadoopGrapes {
}
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.ImportResource
@Configuration
@ConditionalOnMissingBean(org.apache.hadoop.conf.Configuration)
@ImportResource("hadoop-context.xml")
class HadoopContext {
}
package org.springframework.bootstrap.grapes
@GrabConfig(systemClassLoader=true)
// Grab some Tomcat dependencies
@Grab("org.apache.tomcat.embed:tomcat-embed-core:7.0.32")
// JULI logging has sensible defaults in JAVA_HOME, so no need for user to create it
@Grab("org.apache.tomcat.embed:tomcat-embed-logging-juli:7.0.32")
class TomcatGrapes {
}
\ No newline at end of file
package org.springframework.bootstrap.grapes
@Grab("org.springframework:spring-webmvc:4.0.0.BOOTSTRAP-SNAPSHOT")
class WebGrapes {
}
\ No newline at end of file
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:hdp="http://www.springframework.org/schema/hadoop"
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.xsd
http://www.springframework.org/schema/hadoop http://www.springframework.org/schema/hadoop/spring-hadoop.xsd">
<hdp:resource-loader id="hadoopResourceLoader" />
<hdp:configuration>
fs.default.name=${hd.fs:hdfs://localhost:9000}
</hdp:configuration>
<bean id="defaultResourceLoaders" class="org.springframework.data.hadoop.fs.CustomResourceLoaderRegistrar" p:loader-ref="hadoopResourceLoader" />
</beans>
\ No newline at end of file
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.ast.ClassHelper
import groovy.util.logging.Log
def bootstrap = 'org.springframework.bootstrap.grapes.BootstrapGrapes' as Class
void addImport(module, path) {
def name = path.lastIndexOf('.').with {it != -1 ? path[it+1..<path.length()] : path}
if (name=="*") {
// Doesn't work?
name = path.lastIndexOf('.').with {path[0..<it] }
module.addStarImport(name, [])
} else {
module.addImport(name, ClassHelper.make(path), [])
}
}
withConfig(configuration) {
ast(Log)
imports {
normal 'javax.sql.DataSource'
normal 'org.springframework.stereotype.Component'
normal 'org.springframework.stereotype.Controller'
normal 'org.springframework.stereotype.Repository'
normal 'org.springframework.stereotype.Service'
normal 'org.springframework.beans.factory.annotation.Autowired'
normal 'org.springframework.beans.factory.annotation.Value'
normal 'org.springframework.context.annotation.Import'
normal 'org.springframework.context.annotation.ImportResource'
normal 'org.springframework.context.annotation.Profile'
normal 'org.springframework.context.annotation.Scope'
normal 'org.springframework.context.annotation.Configuration'
normal 'org.springframework.context.annotation.Bean'
normal 'org.springframework.bootstrap.CommandLineRunner'
}
def dependencySource = "org.springframework.bootstrap.grapes.Dependencies" as Class // TODO: maybe strategise this
inline(phase:'CONVERSION') { source, context, classNode ->
def module = source.getAST()
if (classNode.name.contains("Hadoop")) {
def hadoop = dependencySource.hadoop() as Class[]
['org.springframework.data.hadoop.mapreduce.JobRunner',
'org.springframework.data.hadoop.mapreduce.JobFactoryBean'
].each { path -> addImport(module, path) }
module.addImport("HadoopConfiguration", ClassHelper.make("org.apache.hadoop.conf.Configuration"), [])
}
classNode.annotations.each {
def name = it.classNode.name
if (name=='Controller' || name=='EnableWebMvc') {
def web = dependencySource.web() as Class[]
['org.springframework.web.bind.annotation.RequestBody',
'org.springframework.web.bind.annotation.RequestParam',
'org.springframework.web.bind.annotation.PathVariable',
'org.springframework.web.bind.annotation.RequestHeader',
'org.springframework.web.bind.annotation.RequestMethod',
'org.springframework.web.bind.annotation.RequestBody',
'org.springframework.web.bind.annotation.ResponseBody',
'org.springframework.web.bind.annotation.ResponseStatus',
'org.springframework.web.bind.annotation.RequestMapping',
'org.springframework.web.bind.annotation.ExceptionHandler',
'org.springframework.web.bind.annotation.ModelAttribute',
'org.springframework.web.bind.annotation.CookieValue',
'org.springframework.web.servlet.config.annotation.EnableWebMvc',
'org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry',
'org.springframework.web.servlet.config.annotation.ViewControllerRegistry',
'org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter'].each { path -> addImport(module, path) }
}
if (name=='EnableBatchProcessing') {
def batch = dependencySource.batch() as Class[]
['org.springframework.batch.repeat.RepeatStatus',
'org.springframework.batch.core.scope.context.ChunkContext',
'org.springframework.batch.core.step.tasklet.Tasklet',
'org.springframework.batch.core.configuration.annotation.StepScope',
'org.springframework.batch.core.configuration.annotation.JobBuilderFactory',
'org.springframework.batch.core.configuration.annotation.StepBuilderFactory',
'org.springframework.batch.core.configuration.annotation.EnableBatchProcessing',
'org.springframework.batch.core.Step',
'org.springframework.batch.core.StepExecution',
'org.springframework.batch.core.StepContribution',
'org.springframework.batch.core.Job',
'org.springframework.batch.core.JobExecution',
'org.springframework.batch.core.JobParameter',
'org.springframework.batch.core.JobParameters',
'org.springframework.batch.core.launch.JobLauncher',
'org.springframework.batch.core.converter.DefaultJobParametersConverter'].each { path -> addImport(module, path) }
}
}
}
}
##############################################################################
## ##
## Groovy Classloading Configuration ##
## ##
##############################################################################
##
## $Revision$ $Date$
##
## Note: do not add classes from java.lang here. No rt.jar and on some
## platforms no tools.jar
##
## See http://groovy.codehaus.org/api/org/codehaus/groovy/tools/LoaderConfiguration.html
## for the file format
# load required libraries
load !{groovy.home}/lib/a*.jar
load !{groovy.home}/lib/b*.jar
load !{groovy.home}/lib/c*.jar
load !{groovy.home}/lib/g*.jar
load !{groovy.home}/lib/h*.jar
load !{groovy.home}/lib/i*.jar
load !{groovy.home}/lib/j*.jar
load !{groovy.home}/lib/q*.jar
load !{groovy.home}/lib/t*.jar
load !{groovy.home}/lib/x*.jar
# load user specific libraries
load !{user.home}/.groovy/lib/*.jar
# tools.jar for ant tasks
load ${tools.jar}
#!/bin/bash
# OS specific support (must be 'true' or 'false').
cygwin=false;
darwin=false;
case "`uname`" in
CYGWIN*)
cygwin=true
;;
Darwin*)
darwin=true
;;
esac
if [ "$SPRING_HOME" == "" ]; then
SPRING_HOME=`cd "$(dirname $0)"/.. && pwd`
fi
SPRING_BIN=$(dirname $0)
export GROOVY_CONF="${SPRING_BIN}"/groovy.conf
SPRING_HANDLER=auto
TARGETDIR=target/classes
if [ -f build.gradle ]; then
TARGETDIR=build/classes/main
fi
mkdir -p "${TARGETDIR%/}"
function find_classfile {
classname="$( echo ${1%%.groovy} | sed -e 's,.*/,,')"
package="$( grep ^package ${1} | sed -e 's,package\s,,g' -e 's,;,,g' -e 's,\.,/,g')"
if [ "${package}" != "" ]; then package="${package}/"; fi
for f in $( find "${TARGETDIR}" -name "${classname}.class" ); do
if [ "${f}" == "${TARGETDIR}/${package}${classname}.class" ]; then
echo $f; return 0
fi
done
}
function is_compile_needed {
config=$1
STATOPTS="-c %X"
if $darwin; then STATOPTS="-f %Dm"; fi
# Compile .groovy files if necessary
if [ ! -f ${config} ]; then
echo "File ${config} does not exist. Did you point at the wrong file?"
exit 3
else
classfile=$( find_classfile ${config} )
if [ ! -f "${classfile}" -o $(stat "${STATOPTS}" ${config}) -gt $(stat "${STATOPTS}" ${classfile} 2>/dev/null || echo 0) ]; then
return 0
fi
fi
return 1
}
function is_option {
echo "$1" | grep -q "^--.*"
}
function is_groovy {
[ "${1%%.groovy}" != "${1}" ]
}
function convert_config_to_class {
classfile=$( find_classfile ${config} )
if [ -z "${classfile}" ]; then
echo "No class found for ${config}. Compiler failed or class not defined?"
exit 1
fi
config="${classfile#${TARGETDIR}/}"
while [ "${config%/*}" != "${config}" ]; do
config="${config%/*}"."${config##*/}"
done
}
config=$1; shift
configs=()
compilables=()
while [ "$config" != "" ]; do
if is_groovy "$config"; then
if is_compile_needed "${config}"; then
compilables[${#compilables[@]}]="${config}"
fi
configs[${#configs[@]}]="${config}"
elif is_option "${config}"; then
case "${config%=*}" in
"--handler") SPRING_HANDLER="${config#*=}";;
esac
else
args[${#args[@]}]="${config}"
fi
config=$1; shift
done
CLASSPATH="${SPRING_BIN}":"${TARGETDIR}"
for f in "${SPRING_HOME}"/lib/*.jar; do
CLASSPATH="${CLASSPATH}":$f
done
for f in "${SPRING_HOME}"/*.jar; do
CLASSPATH="${CLASSPATH}":$f
done
if $cygwin; then
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
fi
if [ "${#compilables[@]}" -gt 0 ]; then
groovyc -cp "${CLASSPATH}" --configscript "$SPRING_BIN"/customizer.groovy -d "${TARGETDIR}" "${compilables[@]}"
fi
config_classes=("org.springframework.bootstrap.grapes.BootstrapAutoConfiguration.class")
for config in "${configs[@]}"; do
convert_config_to_class
config_classes[${#config_classes[@]}]="${config}"
done
if [ "${#config_classes[@]}" == "0" ]; then
echo "No files to run."
exit 2
fi
exec groovy -cp "${CLASSPATH}" -Dspring.home="${SPRING_HOME}" --configscript "$SPRING_BIN"/customizer.groovy "$SPRING_BIN"/spring-"${SPRING_HANDLER}".groovy "${config_classes[@]}" "${args[@]}"
// Get the args and turn them into classes
def configs = []
def parameters = []
args.each { arg ->
if (arg.endsWith(".class")) {
configs << arg.replaceAll(".class", "")
} else {
parameters << arg
}
}
// Dynamically grab some dependencies
def dependencySource = "org.springframework.bootstrap.grapes.Dependencies" as Class // TODO: maybe strategise this
def dependencies = [*dependencySource.defaults(), *dependencySource.dependencies(configs)]
configs = dependencies + configs
// Do this before any Spring auto stuff is used in case it enhances the classpath
configs = configs as Class[]
parameters = parameters as String[]
// Now run the application
def applicationClass = "org.springframework.bootstrap.SpringApplication" as Class
applicationClass.run(configs, parameters)
// Spring stuff needs to be on the system classloader apparently (when using @Configuration)
@GrabResolver(name='spring-milestone', root='http://maven.springframework.org/milestone')
@GrabConfig(systemClassLoader=true)
@Grab("org.springframework:spring-context:4.0.0.BOOTSTRAP-SNAPSHOT")
@GrabExclude("commons-logging:commons-logging")
@Grab("org.slf4j:jcl-over-slf4j:1.6.1")
@Grab("org.slf4j:slf4j-jdk14:1.6.1")
import org.springframework.context.annotation.AnnotationConfigApplicationContext
// Now create a Spring context
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()
// Get the args and turn them into classes
def configs = []
def parameters = []
boolean endconfigs = false
args.each { arg ->
if (arg.endsWith(".class")) {
configs += arg.replaceAll(".class", "")
} else {
parameters += arg
}
}
configs = configs as Class[]
parameters = parameters as String[]
// Register the config classes, can be @Configuration or @Component etc.
ctx.register(configs)
ctx.refresh()
@Controller
class App {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!"
}
}
\ No newline at end of file
@Configuration
class Empty {
}
\ No newline at end of file
@Grab("org.hsqldb:hsqldb-j5:2.0.0")
@EnableBatchProcessing
class JobConfig {
@Autowired
private JobBuilderFactory jobs
@Autowired
private StepBuilderFactory steps
@Bean
protected Tasklet tasklet() {
return new Tasklet() {
RepeatStatus execute(StepContribution contribution, ChunkContext context) {
return RepeatStatus.FINISHED
}
}
}
@Bean
Job job() throws Exception {
return jobs.get("job").start(step1()).build()
}
@Bean
protected Step step1() throws Exception {
return steps.get("step1").tasklet(tasklet()).build()
}
}
import java.io.File;
import org.springframework.bootstrap.CommandLineRunner;
@Component
class Signal implements CommandLineRunner {
private File messages = new File("target/messages")
boolean ready = false
@Override
void run(String... args) {
messages.mkdirs()
new File(messages, "ready").write("Ready!")
ready = true
}
}
\ No newline at end of file
/*
* Cloud Foundry 2012.02.03 Beta
* Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*/
package org.springframework.bootstrap.groovy
/**
* <p>As part of the gvm test suite we need to launch a bash shell and execute
* multiple commands in it. This is tricky to do using Java's support for
* working with external processes as the API can't tell you when a command
* has finished executing.</p>
* <p>This class provides some hacks that allow you to serially execute commands
* in an external bash process in a fairly reliable manner and to retrieve the
* output of those commands.</p>
*/
class BashEnv {
static final PROMPT = ""
static final EXIT_CODE_CMD = 'echo "Exit code is: $?"'
static final EXIT_CODE_PATTERN = ~/Exit code is: (\d+)\s*${PROMPT}?$/
private final Object outputLock = new Object()
def exitCode
def process
def processOutput = new StringBuilder()
def commandOutput
// Command timeout in milliseconds
def timeout = 5000
def workDir
def env
BashEnv(workDir, Map env) {
this.workDir = workDir as File
env = env + [PS1: PROMPT]
this.env = env.collect { k, v -> k + '=' + v }
}
/**
* Starts the external bash process.
*/
void start() {
process = ["bash", "--noprofile", "--norc", "-i"].execute(env, workDir)
consumeProcessStream(process.inputStream)
consumeProcessStream(process.errorStream)
}
/**
* Stops the external bash process and waits for it to finish.
*/
void stop() {
execute("exit")
process.waitFor()
}
/**
* Sends a command line to the external bash process and returns once the
* command has finished executing. If the command is interactive and requires
* input during it's execution (for example a y/n answer to a question) you
* can provide that input as a list of strings.
*/
void execute(String cmdline, List inputs = []) {
resetOutput()
if (cmdline != "exit") {
exitCode = null
}
process.outputStream << cmdline << "\n"
process.outputStream.flush()
if (cmdline != "exit") {
for (input in inputs) {
process.outputStream << input << "\n"
}
process.outputStream << EXIT_CODE_CMD << "\n"
process.outputStream.flush()
}
def start = System.currentTimeMillis()
while (cmdline != "exit") {
Thread.sleep 100
synchronized (outputLock) {
// Remove all the extraneous text that's not related to the
// command's output. This includes the command string itself,
// the 'echo' command to display the command's exit code, and
// the exit code line.
removeFromOutput(cmdline + "\n")
removeFromOutput(PROMPT + EXIT_CODE_CMD + "\n")
def str = processOutput.toString()
def m = EXIT_CODE_PATTERN.matcher(str)
commandOutput = str
if (m) {
exitCode = m[0][1]
// Remove this exit code line from the output.
commandOutput = m.replaceAll('')
break
}
// If the command times out, we should break out of the loop and
// display whatever output has already been produced.
if (System.currentTimeMillis() - start > timeout) {
commandOutput = "ALERT! Command timed out. Last output was:\n\n${processOutput}"
break
}
}
}
}
/**
* Returns the exit code of the last command that was executed.
*/
int getStatus() {
if (!exitCode) throw new IllegalStateException("Did you run execute() before getting the status?")
return exitCode.toInteger()
}
/**
* Returns the text output (both stdout and stderr) of the last command
* that was executed.
*/
String getOutput() {
return commandOutput
}
/**
* Clears the saved command output.
*/
void resetOutput() {
synchronized (outputLock) {
processOutput = new StringBuilder()
}
}
private void consumeProcessStream(final InputStream stream) {
char[] buffer = new char[256]
Thread.start {
def reader = new InputStreamReader(stream)
def charsRead = 0
while (charsRead != -1) {
charsRead = reader.read(buffer, 0, 256)
if (charsRead > 0) {
synchronized (outputLock) {
processOutput.append(buffer, 0, charsRead)
}
}
}
}
}
private void removeFromOutput(String line) {
synchronized (outputLock) {
def pos = processOutput.indexOf(line)
if (pos != -1) {
processOutput.delete(pos, pos + line.size() - 1)
}
}
}
}
/*
* Cloud Foundry 2012.02.03 Beta
* Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*/
package org.springframework.bootstrap.groovy;
import static org.junit.Assert.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import org.junit.After
import org.junit.Before
import org.junit.Test
/**
* @author Dave Syer
*
*/
class ScriptTests {
private BashEnv bash
private ExecutorService executor = Executors.newFixedThreadPool(2)
@Before
void init() {
bash = new BashEnv(".", [SPRING_HOME: "target"])
bash.start()
bash.execute("export GVM_DIR=~/.gvm")
bash.execute("source ~/.gvm/bin/gvm-init.sh")
assertEquals("You need to install gvm to run these tests", 0, bash.status)
bash.execute("gvm use groovy 2.1.0")
assertEquals("You need to do this before running the tests: > gvm install groovy 2.1.0", 0, bash.status)
}
@After
void clean() {
bash?.stop()
}
@Test
void testVanillaApplicationContext() {
execute(bash, "src/main/scripts/spring src/test/apps/Empty.groovy")
assertEquals(0, bash.status)
}
@Test
void testBatchApplicationContext() {
execute(bash, "src/main/scripts/spring src/test/apps/JobConfig.groovy foo=bar")
assertEquals(0, bash.status)
assertTrue(bash.output.contains("[SimpleJob: [name=job]] completed with the following parameters: [{foo=bar}]"))
}
private void execute(BashEnv bash, String cmdline) {
bash.execute(cmdline)
if (bash.exitCode && bash.status!=0) {
println "Unsuccessful execution (${cmdline}). Output: \n${bash.output}"
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-bootstrap-launcher</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${project.basedir}/..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<type>maven-plugin</type>
</dependency>
</dependencies>
</project>
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Classes and interfaces to allows random access to a block of data.
*
* @see org.springframework.bootstrap.launcher.data.RandomAccessData
*/
package org.springframework.bootstrap.launcher.data;
This diff is collapsed.
This diff is collapsed.
package org.springframework.bootstrap.sample.data.domain;
public enum Rating {
TERRIBLE, POOR, AVERAGE, GOOD, EXCELLENT,
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment