diff --git a/spring-bootstrap-applications/pom.xml b/spring-bootstrap-applications/pom.xml index 9822177a59b..315bb3f163e 100644 --- a/spring-bootstrap-applications/pom.xml +++ b/spring-bootstrap-applications/pom.xml @@ -21,6 +21,25 @@ spring-bootstrap-jpa-application spring-bootstrap-web-application + + + + org.springframework.bootstrap + spring-bootstrap + ${spring.bootstrap.version} + + + org.springframework.bootstrap + spring-bootstrap-service + ${spring.bootstrap.version} + + + org.springframework.bootstrap + spring-bootstrap-web-application + ${spring.bootstrap.version} + + + junit diff --git a/spring-bootstrap-service/README.md b/spring-bootstrap-service/README.md index 247fda6bd6a..4f42065e87c 100644 --- a/spring-bootstrap-service/README.md +++ b/spring-bootstrap-service/README.md @@ -23,7 +23,7 @@ production, and in other environments. |Database |HSQLDB or H2 | Per classpath, or define a DataSource to override | |Externalized configuration | Properties or YAML | Support for Spring profiles. Bind automatically to @Bean. | |Audit | Spring Security and Spring ApplicationEvent |Flexible abstraction with sensible defaults for security events | -|Validation | JSR-303 | | +|Validation | JSR-303 |If on the classpath | |Management endpoints | Spring MVC | Health, basic metrics, request tracing, shutdown, thread dumps | |Error pages | Spring MVC | Sensible defaults based on exception and status code | |JSON |Jackson 2 | | @@ -35,8 +35,11 @@ production, and in other environments. You will need Java (6 at least) and a build tool (Maven is what we use below, but you are more than welcome to use gradle). These can be -downloaded or installed easily in most operating systems. FIXME: -short instructions for Mac and Linux. +downloaded or installed easily in most operating systems. For Ubuntu: + + $ sudo apt-get install openjdk-6-jdk maven + + ## A basic project @@ -53,19 +56,14 @@ If you are using Maven create a really simple `pom.xml` with 2 dependencies: spring-bootstrap-applications 0.0.1-SNAPSHOT - - 0.0.1-SNAPSHOT - org.springframework.bootstrap spring-bootstrap-web-application - ${spring.bootstrap.version} org.springframework.bootstrap spring-bootstrap-service - ${spring.bootstrap.version} @@ -82,8 +80,8 @@ If you like Gradle, that's fine, and you will know what to do with those dependencies. The first dependency adds Spring Bootstrap auto configuration and the Jetty container to your application, and the second one adds some more opinionated stuff like the default -management endpoints. If you prefer Tomcat FIXME: use a different -dependency. +management endpoints. If you prefer Tomcat you can just add the +embedded Tomcat jars to your classpath instead of Jetty. You should be able to run it already: @@ -143,7 +141,6 @@ use the main method to launch it from your project jar. Just add a the fully qualified name of your `SampleController`, e.g. - 0.0.1-SNAPSHOT com.mycompany.sample.SampleController @@ -171,7 +168,7 @@ on a class and run it. 4. Find feature in Gradle that does the same thing. -5. Use the Spring executable. FIXME: document this maybe. +5. Use the Spring executable. ## Externalizing configuration @@ -301,8 +298,9 @@ In the `pom.xml` it would look like this: (Spring Security java config is still work in progress so we have used -a snapshot. Beware of sudden changes. FIXME: update to full -release.) +a snapshot. Beware of sudden changes.) + + Try it out: @@ -333,4 +331,57 @@ Try it out: Just add `spring-jdbc` and an embedded database to your dependencies: -FIXME: TBD + + org.springframework + spring-jdbc + + + org.hsqldb + hsqldb + + +Then you will be able to inject a `DataSource` into your controller: + + @Controller + @EnableAutoConfiguration + @EnableConfigurationProperties(ServiceProperties.class) + public class SampleController { + + private JdbcTemplate jdbcTemplate; + + @Autowired + public SampleController(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @RequestMapping("/") + @ResponseBody + public Map helloWorld() { + return jdbcTemplate.queryForMap("SELECT * FROM MESSAGES WHERE ID=?", 0); + } + + ... + } + + The app will run (going back to the default security configuration): + + $ curl user:password@localhost:8080/ + {"error":"Internal Server Error", "status":500, "exception":...} + + but there's no data in the database yet and the `MESSAGES` table + doesn't even exist, so there's an error. One easy way to fix it is + to provide a `schema.sql` script in the root of the classpath, e.g. + + create table MESSAGES ( + ID BIGINT NOT NULL PRIMARY KEY, + MESSAGE VARCHAR(255) + ); + INSERT INTO MESSAGES (ID, MESSAGE) VALUES (0, 'Hello Phil'); + +Now when you run the app you get a sensible response: + + $ curl user:password@localhost:8080/ + {"ID":0, "MESSAGE":"Hello Phil"} + +Obviously, this is only the start, but hopefully you have a good grasp +of the basics and are ready to try it out yourself. diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/jdbc/EmbeddedDatabaseAutoConfiguration.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/jdbc/EmbeddedDatabaseAutoConfiguration.java index 957646aef59..03b44e8fcc4 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/jdbc/EmbeddedDatabaseAutoConfiguration.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/jdbc/EmbeddedDatabaseAutoConfiguration.java @@ -20,8 +20,11 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ExecutorService; +import javax.annotation.PostConstruct; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; @@ -29,10 +32,15 @@ import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.core.task.AsyncUtils; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.util.ClassUtils; /** @@ -54,6 +62,23 @@ public class EmbeddedDatabaseAutoConfiguration { private ExecutorService executorService; + @Autowired + private ResourceLoader resourceLoader = new DefaultResourceLoader(); + + @Value("${spring.jdbc.schema:classpath:schema.sql}") + private String schemaLocation = "classpath:schema.sql"; // FIXME: DB platform + + @PostConstruct + protected void initialize() throws Exception { + Resource resource = this.resourceLoader.getResource(this.schemaLocation); + if (resource.exists()) { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.addScript(resource); + populator.setContinueOnError(true); + DatabasePopulatorUtils.execute(populator, dataSource()); + } + } + @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder() diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ConditionalOnResource.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ConditionalOnResource.java new file mode 100644 index 00000000000..718a0007d82 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/ConditionalOnResource.java @@ -0,0 +1,44 @@ +/* + * 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.context.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * {@link Conditional} that only matches when the specified resources are on the + * classpath. + * + * @author Dave Syer + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnResourceCondition.class) +public @interface ConditionalOnResource { + + /** + * The resources that must be present. + * @return the resource paths that must be present. + */ + public String[] resources() default {}; +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/OnResourceCondition.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/OnResourceCondition.java new file mode 100644 index 00000000000..1f4b6750704 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/annotation/OnResourceCondition.java @@ -0,0 +1,66 @@ +/* + * 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.context.annotation; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; + +/** + * {@link Condition} that checks for specific resources. + * + * @author Dave Syer + * @see ConditionalOnResource + */ +class OnResourceCondition implements Condition { + + private ResourceLoader loader = new DefaultResourceLoader(); + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + MultiValueMap attributes = metadata.getAllAnnotationAttributes( + ConditionalOnClass.class.getName(), true); + if (attributes != null) { + List locations = new ArrayList(); + collectValues(locations, attributes.get("resources")); + Assert.isTrue(locations.size() > 0, + "@ConditionalOnResource annotations must specify at least one resource location"); + for (String location : locations) { + if (!this.loader.getResource(location).exists()) { + return false; + } + } + } + return true; + } + + private void collectValues(List names, List values) { + for (Object value : values) { + for (Object item : (Object[]) value) { + names.add((String) item); + } + } + } + +}