This commit is contained in:
Phillip Webb 2014-11-03 17:20:40 -08:00
parent 9b6cb83be5
commit a973fd41f4
17 changed files with 192 additions and 123 deletions

View File

@ -35,7 +35,6 @@ public enum SearchStrategy {
/**
* Search the entire hierarchy
*
*/
ALL;

View File

@ -32,8 +32,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar;
import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
@ -65,7 +65,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.class);
/**
@ -145,22 +145,24 @@ public class DataSourceAutoConfiguration {
}
@Configuration
@ConditionalOnExpression("${spring.datasource.jmxEnabled:true}")
@ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy")
@Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
protected static class TomcatDataSourceJmxConfiguration {
@Bean
public Object dataSourceMBean(DataSource dataSource) {
if (dataSource instanceof DataSourceProxy) {
try {
return ((DataSourceProxy) dataSource).createPool().getJmxPool();
}
catch (SQLException e) {
catch (SQLException ex) {
logger.warn("Cannot expose DataSource to JMX (could not connect)");
}
}
return null;
}
}
/**

View File

@ -17,6 +17,12 @@
"description": "Execute all Spring Batch jobs in the context on startup.",
"defaultValue": true,
},
{
"name": "spring.datasource.jmx-enabled",
"dataType": "java.lang.Boolean",
"description": "Enables JMX support (if provided by the underlying pool).",
"defaultValue": true,
},
{
"name": "spring.data.elasticsearch.repositories.enabled",
"dataType": "java.lang.Boolean",

View File

@ -29,8 +29,6 @@ import org.springframework.boot.cli.command.options.OptionHelp;
*/
public abstract class AbstractCommand implements Command {
protected static final String NEW_LINE = System.getProperty("line.separator");
private final String name;
private final String description;
@ -60,11 +58,6 @@ public abstract class AbstractCommand implements Command {
return null;
}
@Override
public String getExamples() {
return null;
}
@Override
public String getHelp() {
return null;
@ -75,4 +68,9 @@ public abstract class AbstractCommand implements Command {
return Collections.emptyList();
}
@Override
public Collection<HelpExample> getExamples() {
return null;
}
}

View File

@ -48,13 +48,6 @@ public interface Command {
*/
String getUsageHelp();
/**
* Return some examples for the command. This can be a multi-lined string with
* one example per line, starting with the description and ending with the
* example.
*/
String getExamples();
/**
* Gets full help text for the command, e.g. a longer description and one line per
* option.
@ -66,6 +59,11 @@ public interface Command {
*/
Collection<OptionHelp> getOptionsHelp();
/**
* Return some examples for the command.
*/
Collection<HelpExample> getExamples();
/**
* Run the command.
* @param args command arguments (this will not include the command itself)

View File

@ -0,0 +1,47 @@
/*
* Copyright 2012-2014 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.boot.cli.command;
/**
* An example that can be displayed in the help.
*
* @author Phillip Webb
* @since 1.2.0
*/
public class HelpExample {
private final String description;
private final String example;
/**
* @param description The description (in the form "to ....")
* @param example the example
*/
public HelpExample(String description, String example) {
this.description = description;
this.example = example;
}
public String getDescription() {
return this.description;
}
public String getExample() {
return this.example;
}
}

View File

@ -25,6 +25,7 @@ import java.util.Set;
import org.springframework.boot.cli.command.AbstractCommand;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.CommandRunner;
import org.springframework.boot.cli.command.HelpExample;
import org.springframework.boot.cli.command.NoHelpCommandArgumentsException;
import org.springframework.boot.cli.command.NoSuchCommandException;
import org.springframework.boot.cli.command.options.OptionHelp;
@ -101,15 +102,20 @@ public class HelpCommand extends AbstractCommand {
+ " " + command.getUsageHelp());
Log.info("");
}
if (command.getExamples() != null) {
Log.info("example(s):");
Log.info("");
Log.info(command.getExamples());
Log.info("");
}
if (command.getHelp() != null) {
Log.info(command.getHelp());
}
Collection<HelpExample> examples = command.getExamples();
if (examples != null) {
Log.info(examples.size() == 1 ? "example:" : "examples:");
Log.info("");
for (HelpExample example : examples) {
Log.info(" " + example.getDescription() + ":");
Log.info(" $ " + example.getExample());
Log.info("");
}
Log.info("");
}
return ExitStatus.OK;
}
}

View File

@ -19,12 +19,14 @@ package org.springframework.boot.cli.command.init;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.HelpExample;
import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.options.OptionHandler;
import org.springframework.boot.cli.command.status.ExitStatus;
@ -54,13 +56,16 @@ public class InitCommand extends OptionParsingCommand {
}
@Override
public String getExamples() {
StringBuilder sb = new StringBuilder();
sb.append("Lists the capabilities of the service: spring init --list").append(NEW_LINE);
sb.append("Creates a default project: spring init").append(NEW_LINE);
sb.append("Creates a web my-app.zip: spring init -d=web my-app.zip").append(NEW_LINE);
sb.append("Creates a web/data-jpa gradle project unpacked: spring init -d=web,jpa --build=gradle my-dir/");
return sb.toString();
public Collection<HelpExample> getExamples() {
List<HelpExample> examples = new ArrayList<HelpExample>();
examples.add(new HelpExample("To list all the capabilities of the service",
"spring init --list"));
examples.add(new HelpExample("To creates a default project", "spring init"));
examples.add(new HelpExample("To create a web my-app.zip",
"spring init -d=web my-app.zip"));
examples.add(new HelpExample("To create a web/data-jpa gradle project unpacked",
"spring init -d=web,jpa --build=gradle my-dir/"));
return examples;
}
static class InitOptionHandler extends OptionHandler {

View File

@ -81,18 +81,18 @@ class ProjectGenerationRequest {
if (output != null && output.endsWith("/")) {
this.output = output.substring(0, output.length() - 1);
this.extract = true;
} else {
}
else {
this.output = output;
}
}
/**
* Specify if the project archive should be extract in the output location. If
* the {@link #getOutput() output} ends with "/", the project is extracted
* automatically.
* Specify if the project archive should be extract in the output location. If the
* {@link #getOutput() output} ends with "/", the project is extracted automatically.
*/
public boolean isExtract() {
return extract;
return this.extract;
}
public void setExtract(boolean extract) {
@ -221,8 +221,7 @@ class ProjectGenerationRequest {
return builder.build();
}
catch (URISyntaxException e) {
throw new ReportableException("Invalid service URL (" + e.getMessage()
+ ")");
throw new ReportableException("Invalid service URL (" + e.getMessage() + ")");
}
}
@ -230,8 +229,8 @@ class ProjectGenerationRequest {
if (this.type != null) {
ProjectType result = metadata.getProjectTypes().get(this.type);
if (result == null) {
throw new ReportableException(("No project type with id '"
+ this.type + "' - check the service capabilities (--list)"));
throw new ReportableException(
("No project type with id '" + this.type + "' - check the service capabilities (--list)"));
}
return result;
}
@ -248,8 +247,8 @@ class ProjectGenerationRequest {
return types.values().iterator().next();
}
else if (types.size() == 0) {
throw new ReportableException("No type found with build '"
+ this.build + "' and format '" + this.format
throw new ReportableException("No type found with build '" + this.build
+ "' and format '" + this.format
+ "' check the service capabilities (--list)");
}
else {

View File

@ -28,10 +28,12 @@ import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
/**
* Helper class used to generate the project.
*
* @author Stephane Nicoll
* @since 1.2.0
*/
public class ProjectGenerator {
class ProjectGenerator {
private static final String ZIP_MIME_TYPE = "application/zip";
@ -41,9 +43,11 @@ public class ProjectGenerator {
this.initializrService = initializrService;
}
public void generateProject(ProjectGenerationRequest request, boolean force) throws IOException {
public void generateProject(ProjectGenerationRequest request, boolean force)
throws IOException {
ProjectGenerationResponse response = this.initializrService.generate(request);
String fileName = (request.getOutput() != null ? request.getOutput() : response.getFileName());
String fileName = (request.getOutput() != null ? request.getOutput() : response
.getFileName());
if (request.isExtract()) {
if (isZipArchive(response)) {
extractProject(response, request.getOutput(), force);
@ -51,7 +55,8 @@ public class ProjectGenerator {
}
else {
Log.info("Could not extract '" + response.getContentType() + "'");
fileName = response.getFileName(); // Use value from the server since we can't extract it
fileName = response.getFileName(); // Use value from the server since we
// can't extract it
}
}
if (fileName == null) {

View File

@ -108,8 +108,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests {
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
"application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(request);
assertEquals(ExitStatus.OK,
this.command.run(folder.getAbsolutePath()+ "/"));
assertEquals(ExitStatus.OK, this.command.run(folder.getAbsolutePath() + "/"));
File archiveFile = new File(folder, "test.txt");
assertTrue("Archive not extracted properly " + folder.getAbsolutePath()
+ " not found", archiveFile.exists());
@ -204,8 +203,7 @@ public class InitCommandTests extends AbstractHttpClientMockTests {
MockHttpProjectGenerationRequest request = new MockHttpProjectGenerationRequest(
"application/zip", "demo.zip", archive);
mockSuccessfulProjectGeneration(request);
assertEquals(
ExitStatus.OK,
assertEquals(ExitStatus.OK,
this.command.run("--force", "--extract", folder.getAbsolutePath()));
assertTrue("File should have changed", fileLength != conflict.length());
}

View File

@ -142,7 +142,7 @@ public class ProjectGenerationRequestTests {
public void typeAndBuildAndFormat() {
InitializrServiceMetadata metadata = readMetadata();
setBuildAndFormat("gradle", "project");
request.setType("maven-build");
this.request.setType("maven-build");
assertEquals(createUrl("/pom.xml?type=maven-build"),
this.request.generateUrl(metadata));
}

View File

@ -202,6 +202,7 @@ content into your application; rather pick only the properties that you need.
spring.datasource.time-between-eviction-runs-millis=
spring.datasource.min-evictable-idle-time-millis=
spring.datasource.max-wait=
spring.datasource.jmx-enabled=true # Export JMX MBeans (if supported)
# MONGODB ({sc-spring-boot-autoconfigure}/mongo/MongoProperties.{sc-ext}[MongoProperties])
spring.data.mongodb.host= # the db host

View File

@ -299,10 +299,14 @@ run the app. A http://issues.gradle.org/browse/GRADLE-2871[bug in Gradle] curren
prevents you from using Gradle newer than 1.6.
[[cloud-deployment-gae]]
== Google App Engine
Google App Engine is tied to the Servlet 2.5 API, so you can't deploy a Spring Application
there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>> of this guide.
there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>>
of this guide.
[[cloud-deployment-whats-next]]
== What to read next

View File

@ -2065,6 +2065,64 @@ context.
Applications that are not already Spring applications might be convertible to a Spring
Boot application, and the guidance above might help, but your mileage may vary.
[[howto-servlet-2-5]]
=== Deploying a WAR in an Old (Servlet 2.5) Container
include::servlet-2.5.adoc[]
Spring Boot uses Servet 3.0 APIs to initialize the `ServletContext` (register `Servlets`
etc.) so you can't use the same application out of the box in a Servlet 2.5 container.
It *is* however possible to run a Spring Boot application on an older container with some
special tools. If you include `org.springframework.boot:spring-boot-legacy` as a
dependency (https://github.com/scratches/spring-boot-legacy[maintained separately] to the
core of Spring Boot and currently available at 1.0.0.RELEASE), all you should need to do
is create a `web.xml` and declare a context listener to create the application context and
your filters and servlets. The context listener is a special purpose one for Spring Boot,
but the rest of it is normal for a Spring application in Servlet 2.5. Example:
[source,xml,indent=0]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>demo.Application</param-value>
</context-param>
<listener>
<listener-class>org.springframework.boot.legacy.context.web.SpringBootContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>metricFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filte>r
<filter-mapping>
<filter-name>metricFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
----
In this example we are using a single application context (the one created by the context
listener) and attaching it to the `DispatcherServlet` using an init parameter. This is
normal in a Spring Boot application (you normally only have one application context).

View File

@ -1,61 +0,0 @@
Spring Boot uses Servet 3.0 APIs to initialize the `ServletContext`
(register `Servlets` etc.) so you can't use the same application out
of the box in a Servlet 2.5 container. It *is* however possible to run
a Spring Boot application on an older container with some special
tools. If you include `org.springframework.boot:spring-boot-legacy` as
a dependency
(https://github.com/scratches/spring-boot-legacy[maintained
separately] to the core of Spring Boot and currently available at
1.0.0.RELEASE), all you should need to do is create a `web.xml` and
declare a context listener to create the application context and your
filters and servlets. The context listener is a special purpose one
for Spring Boot, but the rest of it is normal for a Spring application
in Servlet 2.5. Example:
[source,xml,indent=0]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>demo.Application</param-value>
</context-param>
<listener>
<listener-class>org.springframework.boot.legacy.context.web.SpringBootContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>metricFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filte>r
<filter-mapping>
<filter-name>metricFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
----
In this example we are using a single application context (the one created by the context listener) and
attaching it to the `DispatcherServlet` using an init parameter. This is normal in a Spring Boot
application (you normally only have one application conext).

View File

@ -1,10 +1,5 @@
package sample.data.jpa;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
@ -22,6 +17,11 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration test to run the application.
*
@ -32,7 +32,7 @@ import org.springframework.web.context.WebApplicationContext;
@SpringApplicationConfiguration(classes = SampleDataJpaApplication.class)
@WebAppConfiguration
// Enable JMX so we can test the MBeans (you can't do this in a properties file)
@TestPropertySource(properties="spring.jmx.enabled:true")
@TestPropertySource(properties = "spring.jmx.enabled:true")
@ActiveProfiles("scratch")
// Separate profile for web tests to avoid clashing databases
public class SampleDataJpaApplicationTests {
@ -56,8 +56,12 @@ public class SampleDataJpaApplicationTests {
@Test
public void testJmx() throws Exception {
assertEquals(1, ManagementFactory.getPlatformMBeanServer().queryMBeans(
new ObjectName("jpa.sample:type=ConnectionPool,*"), null).size());
assertEquals(
1,
ManagementFactory
.getPlatformMBeanServer()
.queryMBeans(new ObjectName("jpa.sample:type=ConnectionPool,*"),
null).size());
}
}