Merge branch '1.4.x' into 1.5.x

pull/8346/head
Andy Wilkinson 8 years ago
commit 68af831059

@ -22,6 +22,7 @@
</properties>
<modules>
<module>spring-boot-devtools-tests</module>
<module>spring-boot-integration-tests-embedded-servlet-container</module>
<module>spring-boot-gradle-tests</module>
<module>spring-boot-launch-script-tests</module>
<module>spring-boot-security-tests</module>

@ -0,0 +1,66 @@
<?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.boot</groupId>
<artifactId>spring-boot-integration-tests</artifactId>
<version>1.5.2.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-integration-tests-embedded-servlet-container</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Embedded Servlet Container Integration Tests</name>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,72 @@
/*
* Copyright 2012-2017 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 com.example;
import java.io.IOException;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.EmbeddedServerPortFileWriter;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* Test application for verifying an embedded container's static resource handling.
*
* @author Andy Wilkinson
*/
@SpringBootApplication
public class ResourceHandlingApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ResourceHandlingApplication.class)
.properties("server.port:0")
.listeners(new EmbeddedServerPortFileWriter("target/server.port"))
.run(args);
}
@Bean
public ServletRegistrationBean resourceServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(
new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
URL resource = getServletContext()
.getResource(req.getQueryString());
if (resource == null) {
resp.sendError(404);
}
else {
resp.getWriter().println(resource);
resp.getWriter().flush();
}
}
});
registration.addUrlMappings("/servletContext");
return registration;
}
}

@ -0,0 +1,96 @@
/*
* Copyright 2012-2017 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.context.embedded;
import java.io.File;
import java.io.FileReader;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.List;
import org.junit.rules.ExternalResource;
import org.springframework.util.FileCopyUtils;
/**
* Base {@link ExternalResource} for launching a Spring Boot application as part of a
* JUnit test.
*
* @author Andy Wilkinson
*/
abstract class AbstractApplicationLauncher extends ExternalResource {
private final File serverPortFile = new File("target/server.port");
private final ApplicationBuilder applicationBuilder;
private Process process;
private int httpPort;
protected AbstractApplicationLauncher(ApplicationBuilder applicationBuilder) {
this.applicationBuilder = applicationBuilder;
}
@Override
protected final void before() throws Throwable {
this.process = startApplication();
}
@Override
protected final void after() {
this.process.destroy();
}
public final int getHttpPort() {
return this.httpPort;
}
protected abstract List<String> getArguments(File archive);
private Process startApplication() throws Exception {
this.serverPortFile.delete();
File archive = this.applicationBuilder.buildApplication();
List<String> arguments = new ArrayList<String>();
arguments.add(System.getProperty("java.home") + "/bin/java");
arguments.addAll(getArguments(archive));
ProcessBuilder processBuilder = new ProcessBuilder(
arguments.toArray(new String[arguments.size()]));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
Process process = processBuilder.start();
this.httpPort = awaitServerPort(process);
return process;
}
private int awaitServerPort(Process process) throws Exception {
long end = System.currentTimeMillis() + 30000;
while (this.serverPortFile.length() == 0) {
if (System.currentTimeMillis() > end) {
throw new IllegalStateException(
"server.port file was not written within 30 seconds");
}
if (!process.isAlive()) {
throw new IllegalStateException("Application failed to launch");
}
Thread.sleep(100);
}
return Integer.parseInt(
FileCopyUtils.copyToString(new FileReader(this.serverPortFile)));
}
}

@ -0,0 +1,110 @@
/*
* Copyright 2012-2017 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.context.embedded;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.codehaus.plexus.util.StringUtils;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
/**
* Base class for embedded servlet container integration tests.
*
* @author Andy Wilkinson
*/
public abstract class AbstractEmbeddedServletContainerIntegrationTests {
@ClassRule
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public final AbstractApplicationLauncher launcher;
protected final RestTemplate rest = new RestTemplate();
public static Object[] parameters(String packaging) {
List<Object> parameters = new ArrayList<Object>();
parameters.addAll(createParameters(packaging, "jetty", "current"));
parameters.addAll(
createParameters(packaging, "tomcat", "current", "8.0.41", "7.0.75"));
parameters.addAll(createParameters(packaging, "undertow", "current"));
return parameters.toArray(new Object[parameters.size()]);
}
private static List<Object> createParameters(String packaging, String container,
String... versions) {
List<Object> parameters = new ArrayList<Object>();
for (String version : versions) {
ApplicationBuilder applicationBuilder = new ApplicationBuilder(
temporaryFolder, packaging, container, version);
parameters.add(new Object[] {
StringUtils.capitalise(container) + " " + version + " packaged "
+ packaging,
new PackagedApplicationLauncher(applicationBuilder) });
parameters.add(new Object[] {
StringUtils.capitalise(container) + " " + version + " exploded "
+ packaging,
new ExplodedApplicationLauncher(applicationBuilder) });
}
return parameters;
}
protected AbstractEmbeddedServletContainerIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
this.launcher = launcher;
this.rest.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
});
this.rest.setUriTemplateHandler(new UriTemplateHandler() {
@Override
public URI expand(String uriTemplate, Object... uriVariables) {
return URI.create(
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
}
@Override
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
return URI.create(
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
}
});
}
}

@ -0,0 +1,159 @@
/*
* Copyright 2012-2016 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.context.embedded;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import com.samskivert.mustache.Mustache;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Builds a Spring Boot application using Maven. To use this class, the {@code maven.home}
* system property must be set.
*
* @author Andy Wilkinson
*/
class ApplicationBuilder {
private final TemporaryFolder temp;
private final String packaging;
private final String container;
private final String containerVersion;
ApplicationBuilder(TemporaryFolder temp, String packaging, String container,
String containerVersion) {
this.temp = temp;
this.packaging = packaging;
this.container = container;
this.containerVersion = containerVersion;
}
File buildApplication() throws Exception {
File containerFolder = new File(this.temp.getRoot(),
this.container + "-" + this.containerVersion);
if (containerFolder.exists()) {
return new File(containerFolder, "app/target/app-0.0.1." + this.packaging);
}
return doBuildApplication(containerFolder);
}
private File doBuildApplication(File containerFolder)
throws IOException, FileNotFoundException, MavenInvocationException {
File resourcesJar = createResourcesJar();
File appFolder = new File(containerFolder, "app");
appFolder.mkdirs();
writePom(appFolder, resourcesJar);
copyApplicationSource(appFolder);
packageApplication(appFolder);
return new File(appFolder, "target/app-0.0.1." + this.packaging);
}
private File createResourcesJar() throws IOException, FileNotFoundException {
File resourcesJar = new File(this.temp.getRoot(), "resources.jar");
if (resourcesJar.exists()) {
return resourcesJar;
}
JarOutputStream resourcesJarStream = new JarOutputStream(
new FileOutputStream(resourcesJar));
resourcesJarStream.putNextEntry(new ZipEntry("META-INF/resources/"));
resourcesJarStream.closeEntry();
resourcesJarStream.putNextEntry(
new ZipEntry("META-INF/resources/nested-meta-inf-resource.txt"));
resourcesJarStream.write("nested".getBytes());
resourcesJarStream.closeEntry();
resourcesJarStream.close();
return resourcesJar;
}
private void writePom(File appFolder, File resourcesJar)
throws FileNotFoundException, IOException {
Map<String, Object> context = new HashMap<String, Object>();
context.put("packaging", this.packaging);
context.put("container", this.container);
context.put("bootVersion", Versions.getBootVersion());
context.put("resourcesJarPath", resourcesJar.getAbsolutePath());
context.put("containerVersion",
"current".equals(this.containerVersion) ? ""
: String.format("<%s.version>%s</%s.version>", this.container,
this.containerVersion, this.container));
context.put("additionalDependencies", getAdditionalDependencies());
FileWriter out = new FileWriter(new File(appFolder, "pom.xml"));
Mustache.compiler().escapeHTML(false)
.compile(new FileReader("src/test/resources/pom-template.xml"))
.execute(context, out);
out.close();
}
private List<Map<String, String>> getAdditionalDependencies() {
List<Map<String, String>> additionalDependencies = new ArrayList<Map<String, String>>();
if ("tomcat".equals(this.container) && !"current".equals(this.containerVersion)) {
Map<String, String> juli = new HashMap<String, String>();
juli.put("groupId", "org.apache.tomcat");
juli.put("artifactId", "tomcat-juli");
juli.put("version", "${tomcat.version}");
additionalDependencies.add(juli);
}
return additionalDependencies;
}
private void copyApplicationSource(File appFolder) throws IOException {
File examplePackage = new File(appFolder, "src/main/java/com/example");
examplePackage.mkdirs();
FileCopyUtils.copy(
new File("src/test/java/com/example/ResourceHandlingApplication.java"),
new File(examplePackage, "ResourceHandlingApplication.java"));
if ("war".equals(this.packaging)) {
File srcMainWebapp = new File(appFolder, "src/main/webapp");
srcMainWebapp.mkdirs();
FileCopyUtils.copy("webapp resource",
new FileWriter(new File(srcMainWebapp, "webapp-resource.txt")));
}
}
private void packageApplication(File appFolder) throws MavenInvocationException {
InvocationRequest invocation = new DefaultInvocationRequest();
invocation.setBaseDirectory(appFolder);
invocation.setGoals(Collections.singletonList("package"));
InvocationResult execute = new DefaultInvoker().execute(invocation);
assertThat(execute.getExitCode()).isEqualTo(0);
}
}

@ -0,0 +1,85 @@
/*
* Copyright 2012-2017 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.context.embedded;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support using jar
* packaging.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerJarPackagingIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("jar");
}
public EmbeddedServletContainerJarPackagingIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedJarIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/BOOT-INF/lib/resources-1.0.jar", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/BOOT-INF/classes/com/example/ResourceHandlingApplication.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void launcherIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/org/springframework/boot/loader/Launcher.class", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
}

@ -0,0 +1,85 @@
/*
* Copyright 2012-2017 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.context.embedded;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for Spring Boot's embedded servlet container support using war
* packaging.
*
* @author Andy Wilkinson
*/
@RunWith(Parameterized.class)
public class EmbeddedServletContainerWarPackagingIntegrationTests
extends AbstractEmbeddedServletContainerIntegrationTests {
@Parameters(name = "{0}")
public static Object[] parameters() {
return AbstractEmbeddedServletContainerIntegrationTests.parameters("war");
}
public EmbeddedServletContainerWarPackagingIntegrationTests(String name,
AbstractApplicationLauncher launcher) {
super(name, launcher);
}
@Test
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/nested-meta-inf-resource.txt", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void nestedJarIsNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest
.getForEntity("/WEB-INF/lib/resources-1.0.jar", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity(
"/WEB-INF/classes/com/example/ResourceHandlingApplication.class",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
@Test
public void webappResourcesAreAvailableViaHttp() throws Exception {
ResponseEntity<String> entity = this.rest.getForEntity("/webapp-resource.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

@ -0,0 +1,78 @@
/*
* Copyright 2012-2017 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.context.embedded;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StreamUtils;
/**
* {@link AbstractApplicationLauncher} that launches an exploded Spring Boot application
* using Spring Boot's Jar or War launcher.
*
* @author Andy Wilkinson
*/
class ExplodedApplicationLauncher extends AbstractApplicationLauncher {
private final File exploded = new File("target/exploded");
ExplodedApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected List<String> getArguments(File archive) {
String mainClass = archive.getName().endsWith(".war")
? "org.springframework.boot.loader.WarLauncher"
: "org.springframework.boot.loader.JarLauncher";
try {
explodeArchive(archive);
return Arrays.asList("-cp", this.exploded.getAbsolutePath(), mainClass);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void explodeArchive(File archive) throws IOException {
FileSystemUtils.deleteRecursively(this.exploded);
JarFile jarFile = new JarFile(archive);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
File extracted = new File(this.exploded, jarEntry.getName());
if (jarEntry.isDirectory()) {
extracted.mkdirs();
}
else {
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
extractedOutputStream.close();
}
}
jarFile.close();
}
}

@ -0,0 +1,40 @@
/*
* Copyright 2012-2017 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.context.embedded;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* {@link AbstractApplicationLauncher} that launches a packaged Spring Boot application
* using {@code java -jar}.
*
* @author Andy Wilkinson
*/
class PackagedApplicationLauncher extends AbstractApplicationLauncher {
PackagedApplicationLauncher(ApplicationBuilder applicationBuilder) {
super(applicationBuilder);
}
@Override
protected List<String> getArguments(File archive) {
return Arrays.asList("-jar", archive.getAbsolutePath());
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2012-2017 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.context.embedded;
import java.io.FileReader;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.InputSource;
/**
* Provides access to dependency versions by querying the project's pom.
*
* @author Andy Wilkinson
*/
final class Versions {
private Versions() {
}
public static String getBootVersion() {
return evaluateExpression(
"/*[local-name()='project']/*[local-name()='parent']/*[local-name()='version']"
+ "/text()");
}
private static String evaluateExpression(String expression) {
try {
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPathExpression expr = xpath.compile(expression);
String version = expr.evaluate(new InputSource(new FileReader("pom.xml")));
return version;
}
catch (Exception ex) {
throw new IllegalStateException("Failed to evaluate expression", ex);
}
}
}

@ -0,0 +1,68 @@
<?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.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>{{bootVersion}}</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>app</artifactId>
<version>0.0.1</version>
<packaging>{{packaging}}</packaging>
<properties>
<resourcesJarPath>{{resourcesJarPath}}</resourcesJarPath>
{{containerVersion}}
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-{{container}}</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>resources</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${resourcesJarPath}</systemPath>
</dependency>
{{#additionalDependencies}}
<dependency>
<groupId>{{groupId}}</groupId>
<artifactId>{{artifactId}}</artifactId>
<version>{{version}}</version>
</dependency>
{{/additionalDependencies}}
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -20,9 +20,13 @@ import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarFile;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -86,6 +90,31 @@ public abstract class AbstractEmbeddedServletContainerFactory
return getExplodedWarFileDocumentRoot(getCodeSourceArchive());
}
protected List<URL> getUrlsOfJarsWithMetaInfResources() {
ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<URL>();
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
try {
URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
JarURLConnection jarConnection = (JarURLConnection) connection;
JarFile jar = jarConnection.getJarFile();
if (jar.getName().endsWith(".jar")
&& jar.getJarEntry("META-INF/resources") != null) {
staticResourceUrls.add(url);
}
jar.close();
}
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
return staticResourceUrls;
}
File getExplodedWarFileDocumentRoot(File codeSourceFile) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Code archive: " + codeSourceFile);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -60,6 +60,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.webapp.AbstractConfiguration;
@ -408,14 +409,21 @@ public class JettyEmbeddedServletContainerFactory
File root = getValidDocumentRoot();
root = (root != null ? root : createTempDir("jetty-docbase"));
try {
if (!root.isDirectory()) {
Resource resource = JarResource
.newJarResource(Resource.newResource(root));
handler.setBaseResource(resource);
}
else {
handler.setBaseResource(Resource.newResource(root.getCanonicalFile()));
List<Resource> resources = new ArrayList<Resource>();
resources.add(
root.isDirectory() ? Resource.newResource(root.getCanonicalFile())
: JarResource.newJarResource(Resource.newResource(root)));
for (URL resourceJarUrl : this.getUrlsOfJarsWithMetaInfResources()) {
Resource resource = Resource
.newResource(resourceJarUrl + "META-INF/resources");
// Jetty 9.2 and earlier do not support nested jars. See
// https://github.com/eclipse/jetty.project/issues/518
if (resource.exists() && resource.isDirectory()) {
resources.add(resource);
}
}
handler.setBaseResource(new ResourceCollection(
resources.toArray(new Resource[resources.size()])));
}
catch (Exception ex) {
throw new IllegalStateException(ex);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -189,7 +189,7 @@ public class TomcatEmbeddedServletContainerFactory
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File docBase = getValidDocumentRoot();
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
final TomcatEmbeddedContext context = new TomcatEmbeddedContext();
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
@ -219,6 +219,17 @@ public class TomcatEmbeddedServletContainerFactory
addJasperInitializer(context);
context.addLifecycleListener(new StoreMergedWebXmlListener());
}
context.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
TomcatResources.get(context)
.addResourceJars(getUrlsOfJarsWithMetaInfResources());
}
}
});
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
configureContext(context, initializersToUse);
host.addChild(context);
@ -823,7 +834,6 @@ public class TomcatEmbeddedServletContainerFactory
if (servletContext.getAttribute(MERGED_WEB_XML) == null) {
servletContext.setAttribute(MERGED_WEB_XML, getEmptyWebXml());
}
TomcatResources.get(context).addClasspathResources();
}
private String getEmptyWebXml() {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -16,11 +16,10 @@
package org.springframework.boot.context.embedded.tomcat;
import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import javax.naming.directory.DirContext;
import javax.servlet.ServletContext;
@ -37,6 +36,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
*/
abstract class TomcatResources {
@ -46,28 +46,16 @@ abstract class TomcatResources {
this.context = context;
}
/**
* Add resources from the classpath.
*/
public void addClasspathResources() {
ClassLoader loader = getClass().getClassLoader();
if (loader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) loader).getURLs()) {
String file = url.getFile();
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
String jar = url.toString();
if (!jar.startsWith("jar:")) {
// A jar file in the file system. Convert to Jar URL.
jar = "jar:" + jar + "!/";
}
addJar(jar);
}
else if (url.toString().startsWith("file:")) {
String dir = url.toString().substring("file:".length());
if (new File(dir).isDirectory()) {
addDir(dir, url);
}
void addResourceJars(List<URL> resourceJarUrls) {
for (URL url : resourceJarUrls) {
String file = url.getFile();
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
String jar = url.toString();
if (!jar.startsWith("jar:")) {
// A jar file in the file system. Convert to Jar URL.
jar = "jar:" + jar + "!/";
}
addJar(jar);
}
}
}

@ -0,0 +1,74 @@
/*
* Copyright 2012-2017 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.context.embedded.undertow;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import io.undertow.UndertowMessages;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
/**
* A {@ResourceManager} that delegates to multiple {@code ResourceManager} instances.
*
* @author Andy Wilkinson
*/
class CompositeResourceManager implements ResourceManager {
private final List<ResourceManager> resourceManagers;
CompositeResourceManager(ResourceManager... resourceManagers) {
this.resourceManagers = Arrays.asList(resourceManagers);
}
@Override
public void close() throws IOException {
for (ResourceManager resourceManager : this.resourceManagers) {
resourceManager.close();
}
}
@Override
public Resource getResource(String path) throws IOException {
for (ResourceManager resourceManager : this.resourceManagers) {
Resource resource = resourceManager.getResource(path);
if (resource != null) {
return resource;
}
}
return null;
}
@Override
public boolean isResourceChangeListenerSupported() {
return false;
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
}
}

@ -19,6 +19,7 @@ package org.springframework.boot.context.embedded.undertow;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.KeyStore;
@ -49,7 +50,10 @@ import io.undertow.server.handlers.accesslog.AccessLogHandler;
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
import io.undertow.server.handlers.resource.FileResourceManager;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceChangeListener;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.server.handlers.resource.URLResource;
import io.undertow.server.session.SessionManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
@ -461,13 +465,11 @@ public class UndertowEmbeddedServletContainerFactory
private ResourceManager getDocumentRootResourceManager() {
File root = getCanonicalDocumentRoot();
if (root.isDirectory()) {
return new FileResourceManager(root, 0);
}
if (root.isFile()) {
return new JarResourceManager(root);
}
return ResourceManager.EMPTY_RESOURCE_MANAGER;
List<URL> metaInfResourceJarUrls = getUrlsOfJarsWithMetaInfResources();
ResourceManager rootResourceManager = root.isDirectory()
? new FileResourceManager(root, 0) : new JarResourceManager(root);
return new CompositeResourceManager(rootResourceManager,
new MetaInfResourcesResourceManager(metaInfResourceJarUrls));
}
/**
@ -599,6 +601,49 @@ public class UndertowEmbeddedServletContainerFactory
this.useForwardHeaders = useForwardHeaders;
}
/**
* {@link ResourceManager} that exposes resource in {@code META-INF/resources}
* directory of nested (in {@code BOOT-INF/lib} or {@code WEB-INF/lib}) jars.
*/
private static final class MetaInfResourcesResourceManager
implements ResourceManager {
private final List<URL> metaInfResourceJarUrls;
private MetaInfResourcesResourceManager(List<URL> metaInfResourceJarUrls) {
this.metaInfResourceJarUrls = metaInfResourceJarUrls;
}
@Override
public void close() throws IOException {
}
@Override
public Resource getResource(String path) throws IOException {
for (URL url : this.metaInfResourceJarUrls) {
URL resourceUrl = new URL(url + "META-INF/resources" + path);
URLConnection connection = resourceUrl.openConnection();
if (connection.getContentLength() >= 0) {
return new URLResource(resourceUrl, connection, path);
}
}
return null;
}
@Override
public boolean isResourceChangeListenerSupported() {
return false;
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
}
}
/**
* {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer
* ServletContextInitializers}.

Loading…
Cancel
Save