91
spring-boot-loader/src/it/executable-jar/pom.xml
Normal file
91
spring-boot-loader/src/it/executable-jar/pom.xml
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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>
|
||||
<groupId>org.springframework.boot.launcher.it</groupId>
|
||||
<artifactId>executable-jar</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>@project.groupId@</groupId>
|
||||
<artifactId>@project.artifactId@</artifactId>
|
||||
<version>@project.version@</version>
|
||||
<type>jar</type>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>${project.build.directory}/assembly</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/jar-with-dependencies.xml</descriptor>
|
||||
</descriptors>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>org.springframework.boot.load.JarLauncher</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Start-Class>org.springframework.boot.load.it.jar.EmbeddedJarStarter</Start-Class>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jar-with-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-annotations</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>3.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>full</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<useProjectArtifact/>
|
||||
<includes>
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
<unpack>true</unpack>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/assembly</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.jar;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Main class to start the embedded server.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class EmbeddedJarStarter {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Server server = new Server(8080);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
|
||||
webApplicationContext.register(SpringConfiguration.class);
|
||||
DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
|
||||
context.addServlet(new ServletHolder(dispatcherServlet), "/*");
|
||||
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.jar;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Simple example Spring MVC Controller.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Controller
|
||||
public class ExampleController {
|
||||
|
||||
@RequestMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return "Hello Embedded Jar World!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.jar;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Spring configuration.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@ComponentScan
|
||||
public class SpringConfiguration {
|
||||
|
||||
}
|
||||
76
spring-boot-loader/src/it/executable-war/pom.xml
Normal file
76
spring-boot-loader/src/it/executable-war/pom.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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>
|
||||
<groupId>org.springframework.boot.launcher.it</groupId>
|
||||
<artifactId>executable-war</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>org.springframework.boot.load.WarLauncher</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Start-Class>org.springframework.boot.load.it.war.embedded.EmbeddedWarStarter</Start-Class>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>@project.groupId@</groupId>
|
||||
<artifactId>@project.artifactId@</artifactId>
|
||||
<version>@project.version@</version>
|
||||
<type>jar</type>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>${project.build.directory}/${project.artifactId}-${project.version}</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-plus</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-annotations</artifactId>
|
||||
<version>8.1.8.v20121106</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>3.2.0.RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.war;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Simple example Spring MVC Controller.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Controller
|
||||
public class ExampleController {
|
||||
|
||||
@RequestMapping("/")
|
||||
@ResponseBody
|
||||
public String helloWorld() {
|
||||
return "Hello Embedded WAR World!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.war;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* Spring configuration.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@ComponentScan
|
||||
public class SpringConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.war;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Spring {@link WebApplicationInitializer} for classic WAR deployment.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class SpringInitializer implements WebApplicationInitializer {
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
|
||||
webApplicationContext.register(SpringConfiguration.class);
|
||||
servletContext.addServlet("dispatcherServlet",
|
||||
new DispatcherServlet(webApplicationContext)).addMapping("/*");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.war.embedded;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.webapp.Configuration;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.springframework.boot.load.it.war.SpringInitializer;
|
||||
|
||||
/**
|
||||
* Starter to launch the embedded server. NOTE: Jetty annotation scanning is not
|
||||
* compatible with executable WARs so we must specify the {@link SpringInitializer}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class EmbeddedWarStarter {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Server server = new Server(8080);
|
||||
|
||||
WebAppContext webAppContext = new WebAppContext();
|
||||
webAppContext.setContextPath("/");
|
||||
webAppContext.setConfigurations(new Configuration[] {
|
||||
new WebApplicationInitializersConfiguration(SpringInitializer.class) });
|
||||
|
||||
webAppContext.setParentLoaderPriority(true);
|
||||
server.setHandler(webAppContext);
|
||||
server.start();
|
||||
|
||||
server.join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.it.war.embedded;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
import org.eclipse.jetty.webapp.AbstractConfiguration;
|
||||
import org.eclipse.jetty.webapp.Configuration;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
|
||||
/**
|
||||
* Jetty {@link Configuration} that allows Spring {@link WebApplicationInitializer} to be
|
||||
* started. This is required because Jetty annotation scanning does not work with packaged
|
||||
* WARs.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class WebApplicationInitializersConfiguration extends AbstractConfiguration {
|
||||
|
||||
private Class<?>[] webApplicationInitializers;
|
||||
|
||||
public WebApplicationInitializersConfiguration(Class<?> webApplicationInitializer,
|
||||
Class<?>... webApplicationInitializers) {
|
||||
this.webApplicationInitializers = new Class<?>[webApplicationInitializers.length + 1];
|
||||
this.webApplicationInitializers[0] = webApplicationInitializer;
|
||||
System.arraycopy(webApplicationInitializers, 0, this.webApplicationInitializers,
|
||||
1, webApplicationInitializers.length);
|
||||
for (Class<?> i : webApplicationInitializers) {
|
||||
Assert.notNull(i, "WebApplicationInitializer must not be null");
|
||||
Assert.isAssignable(WebApplicationInitializer.class, i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebAppContext context) throws Exception {
|
||||
context.getServletContext().addListener(new ServletContextListener() {
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
try {
|
||||
for (Class<?> webApplicationInitializer : webApplicationInitializers) {
|
||||
WebApplicationInitializer initializer = (WebApplicationInitializer) webApplicationInitializer.newInstance();
|
||||
initializer.onStartup(sce.getServletContext());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
35
spring-boot-loader/src/it/settings.xml
Normal file
35
spring-boot-loader/src/it/settings.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>it-repo</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>local.central</id>
|
||||
<url>@localRepositoryUrl@</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>local.central</id>
|
||||
<url>@localRepositoryUrl@</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</settings>
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* An archive that can be launched by the {@link Launcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see JarFileArchive
|
||||
*/
|
||||
public interface Archive {
|
||||
|
||||
/**
|
||||
* Returns the manifest of the archive.
|
||||
* @return the manifest
|
||||
* @throws IOException
|
||||
*/
|
||||
Manifest getManifest() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns archive entries.
|
||||
* @return the archive entries
|
||||
*/
|
||||
Iterable<Entry> getEntries();
|
||||
|
||||
/**
|
||||
* Returns a URL that can be used to load the archive.
|
||||
* @return the archive URL
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
URL getUrl() throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* Returns a nest archive from on the the contained entries.
|
||||
* @param entry the entry (may be a directory or file)
|
||||
* @return the nested archive
|
||||
* @throws IOException
|
||||
*/
|
||||
Archive getNestedArchive(Entry entry) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a filtered version of the archive.
|
||||
* @param filter the filter to apply
|
||||
* @return a filter archive
|
||||
* @throws IOException
|
||||
*/
|
||||
Archive getFilteredArchive(EntryFilter filter) throws IOException;
|
||||
|
||||
/**
|
||||
* Represents a single entry in the archive.
|
||||
*/
|
||||
public static interface Entry {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the entry represents a directory.
|
||||
* @return if the entry is a directory
|
||||
*/
|
||||
boolean isDirectory();
|
||||
|
||||
/**
|
||||
* Returns the name of the entry
|
||||
* @return the name of the entry
|
||||
*/
|
||||
String getName();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter for archive entries.
|
||||
*/
|
||||
public static interface EntryFilter {
|
||||
|
||||
/**
|
||||
* Apply the jar entry filter.
|
||||
* @param entryName the current entry name. This may be different that the
|
||||
* original entry name if a previous filter has been applied
|
||||
* @param entry the entry to filter
|
||||
* @return the new name of the entry or {@code null} if the entry should not be
|
||||
* included.
|
||||
*/
|
||||
String apply(String entryName, Entry entry);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* {@link Archive} implementation backed by an exploded archive directory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ExplodedArchive implements Archive {
|
||||
|
||||
private static final Set<String> SKIPPED_NAMES = new HashSet<String>(Arrays.asList(
|
||||
".", ".."));
|
||||
|
||||
private static final Object MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF";
|
||||
|
||||
private File root;
|
||||
|
||||
private Map<String, Entry> entries = new LinkedHashMap<String, Entry>();
|
||||
|
||||
private Manifest manifest;
|
||||
|
||||
public ExplodedArchive(File root) {
|
||||
if (!root.exists() || !root.isDirectory()) {
|
||||
throw new IllegalArgumentException("Invalid source folder " + root);
|
||||
}
|
||||
this.root = root;
|
||||
buildEntries(root);
|
||||
this.entries = Collections.unmodifiableMap(this.entries);
|
||||
}
|
||||
|
||||
private ExplodedArchive(File root, Map<String, Entry> entries) {
|
||||
this.root = root;
|
||||
this.entries = Collections.unmodifiableMap(entries);
|
||||
}
|
||||
|
||||
private void buildEntries(File file) {
|
||||
if (!file.equals(this.root)) {
|
||||
String name = file.getAbsolutePath().substring(
|
||||
this.root.getAbsolutePath().length() + 1);
|
||||
if (file.isDirectory()) {
|
||||
name += "/";
|
||||
}
|
||||
this.entries.put(name, new FileEntry(name, file));
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
for (File child : file.listFiles()) {
|
||||
if (!SKIPPED_NAMES.contains(child.getName())) {
|
||||
buildEntries(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
if (this.manifest == null && this.entries.containsKey(MANIFEST_ENTRY_NAME)) {
|
||||
FileEntry entry = (FileEntry) this.entries.get(MANIFEST_ENTRY_NAME);
|
||||
FileInputStream inputStream = new FileInputStream(entry.getFile());
|
||||
try {
|
||||
this.manifest = new Manifest(inputStream);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
return this.manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Entry> getEntries() {
|
||||
return this.entries.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
FilteredURLStreamHandler handler = new FilteredURLStreamHandler();
|
||||
return new URL("file", "", -1, this.root.getAbsolutePath() + "/", handler);
|
||||
// return this.root.toURI().toURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Archive getNestedArchive(Entry entry) throws IOException {
|
||||
File file = ((FileEntry) entry).getFile();
|
||||
return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Archive getFilteredArchive(EntryFilter filter) throws IOException {
|
||||
Map<String, Entry> filteredEntries = new LinkedHashMap<String, Archive.Entry>();
|
||||
for (Map.Entry<String, Entry> entry : this.entries.entrySet()) {
|
||||
String filteredName = filter.apply(entry.getKey(), entry.getValue());
|
||||
if (filteredName != null) {
|
||||
filteredEntries.put(filteredName, new FileEntry(filteredName,
|
||||
((FileEntry) entry.getValue()).getFile()));
|
||||
}
|
||||
}
|
||||
return new ExplodedArchive(this.root, filteredEntries);
|
||||
}
|
||||
|
||||
private class FileEntry implements Entry {
|
||||
|
||||
private final String name;
|
||||
private final File file;
|
||||
|
||||
public FileEntry(String name, File file) {
|
||||
this.name = name;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.file.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link URLStreamHandler} that respects filtered entries.
|
||||
*/
|
||||
private class FilteredURLStreamHandler extends URLStreamHandler {
|
||||
|
||||
public FilteredURLStreamHandler() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
String name = url.getPath().substring(
|
||||
ExplodedArchive.this.root.getAbsolutePath().length() + 1);
|
||||
if (ExplodedArchive.this.entries.containsKey(name)) {
|
||||
return new URL(url.toString()).openConnection();
|
||||
}
|
||||
return new FileNotFoundURLConnection(url, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link URLConnection} used to represent a filtered file.
|
||||
*/
|
||||
private static class FileNotFoundURLConnection extends URLConnection {
|
||||
|
||||
private String name;
|
||||
|
||||
public FileNotFoundURLConnection(URL url, String name) {
|
||||
super(url);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
throw new FileNotFoundException(this.name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.springframework.boot.load.jar.JarEntryFilter;
|
||||
import org.springframework.boot.load.jar.RandomAccessJarFile;
|
||||
|
||||
/**
|
||||
* {@link Archive} implementation backed by a {@link RandomAccessJarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class JarFileArchive implements Archive {
|
||||
|
||||
private final RandomAccessJarFile jarFile;
|
||||
|
||||
private final List<Entry> entries;
|
||||
|
||||
public JarFileArchive(File file) throws IOException {
|
||||
this(new RandomAccessJarFile(file));
|
||||
}
|
||||
|
||||
public JarFileArchive(RandomAccessJarFile jarFile) {
|
||||
this.jarFile = jarFile;
|
||||
ArrayList<Entry> jarFileEntries = new ArrayList<Entry>();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
jarFileEntries.add(new JarFileEntry(entries.nextElement()));
|
||||
}
|
||||
this.entries = Collections.unmodifiableList(jarFileEntries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
return this.jarFile.getManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Entry> getEntries() {
|
||||
return this.entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
return this.jarFile.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Archive getNestedArchive(Entry entry) throws IOException {
|
||||
JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
|
||||
RandomAccessJarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
|
||||
return new JarFileArchive(jarFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Archive getFilteredArchive(final EntryFilter filter) throws IOException {
|
||||
RandomAccessJarFile filteredJar = this.jarFile
|
||||
.getFilteredJarFile(new JarEntryFilter() {
|
||||
@Override
|
||||
public String apply(String name, JarEntry entry) {
|
||||
return filter.apply(name, new JarFileEntry(entry));
|
||||
}
|
||||
});
|
||||
return new JarFileArchive(filteredJar);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Archive.Entry} implementation backed by a {@link JarEntry}.
|
||||
*/
|
||||
private static class JarFileEntry implements Entry {
|
||||
|
||||
private final JarEntry jarEntry;
|
||||
|
||||
public JarFileEntry(JarEntry jarEntry) {
|
||||
this.jarEntry = jarEntry;
|
||||
}
|
||||
|
||||
public JarEntry getJarEntry() {
|
||||
return this.jarEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.jarEntry.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.jarEntry.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
|
||||
* included inside a {@code /lib} directory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class JarLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
return !entry.isDirectory() && entry.getName().startsWith("lib/");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
|
||||
lib.add(0, archive);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new JarLauncher().launch(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
|
||||
import org.springframework.boot.load.jar.RandomAccessJarFile;
|
||||
|
||||
/**
|
||||
* {@link ClassLoader} used by the {@link Launcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
|
||||
/**
|
||||
* Create a new {@link LaunchedURLClassLoader} instance.
|
||||
* @param urls the URLs from which to load classes and resources
|
||||
* @param parent the parent class loader for delegation
|
||||
*/
|
||||
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||
int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot != -1) {
|
||||
String packageName = name.substring(0, lastDot);
|
||||
if (getPackage(packageName) == null) {
|
||||
try {
|
||||
definePackageForFindClass(name, packageName);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.findClass(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a package before a {@code findClass} call is made. This is necessary to
|
||||
* ensure that the appropriate manifest for nested JARs associated with the package.
|
||||
*
|
||||
* @param name the class name being found
|
||||
* @param packageName the pacakge
|
||||
*/
|
||||
private void definePackageForFindClass(final String name, final String packageName) {
|
||||
try {
|
||||
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
||||
public Object run() throws ClassNotFoundException {
|
||||
String path = name.replace('.', '/').concat(".class");
|
||||
for (URL url : getURLs()) {
|
||||
try {
|
||||
if (url.getContent() instanceof RandomAccessJarFile) {
|
||||
RandomAccessJarFile jarFile = (RandomAccessJarFile) url
|
||||
.getContent();
|
||||
if (jarFile.getManifest() != null
|
||||
&& jarFile.getJarEntry(path) != null) {
|
||||
definePackage(packageName, jarFile.getManifest(), url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, AccessController.getContext());
|
||||
}
|
||||
catch (java.security.PrivilegedActionException pae) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Base class for launchers that can start an application with a fully configured
|
||||
* classpath.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public abstract class Launcher {
|
||||
|
||||
private Logger logger = Logger.getLogger(Launcher.class.getName());
|
||||
|
||||
/**
|
||||
* The main runner class. This must be loaded by the created ClassLoader so cannot be
|
||||
* directly referenced.
|
||||
*/
|
||||
private static final String RUNNER_CLASS = Launcher.class.getPackage().getName()
|
||||
+ ".MainMethodRunner";
|
||||
|
||||
/**
|
||||
* Launch the application. This method is the initial entry point that should be
|
||||
* called by a subclass {@code public static void main(String[] args)} method.
|
||||
* @param args the incoming arguments
|
||||
*/
|
||||
public void launch(String[] args) {
|
||||
try {
|
||||
launch(args, getClass().getProtectionDomain());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the application given the protection domain.
|
||||
* @param args the incoming arguments
|
||||
* @param protectionDomain the protection domain
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void launch(String[] args, ProtectionDomain protectionDomain)
|
||||
throws Exception {
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
URL codeSourceLocation = (codeSource == null ? null : codeSource.getLocation());
|
||||
String codeSourcePath = (codeSourceLocation == null ? null : codeSourceLocation
|
||||
.getPath());
|
||||
if (codeSourcePath == null) {
|
||||
throw new IllegalStateException("Unable to determine code source archive");
|
||||
}
|
||||
File root = new File(codeSourcePath);
|
||||
if (!root.exists()) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to determine code source archive from " + root);
|
||||
}
|
||||
Archive archive = (root.isDirectory() ? new ExplodedArchive(root)
|
||||
: new JarFileArchive(root));
|
||||
launch(args, archive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the application given the archive file
|
||||
* @param args the incoming arguments
|
||||
* @param archive the underlying (zip/war/jar) archive
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void launch(String[] args, Archive archive) throws Exception {
|
||||
List<Archive> lib = new ArrayList<Archive>();
|
||||
for (Archive.Entry entry : archive.getEntries()) {
|
||||
if (isNestedArchive(entry)) {
|
||||
this.logger.fine("Adding: " + entry.getName());
|
||||
lib.add(archive.getNestedArchive(entry));
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.fine("Added " + lib.size() + " entries");
|
||||
postProcessLib(archive, lib);
|
||||
ClassLoader classLoader = createClassLoader(lib);
|
||||
launch(args, archive, classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified {@link JarEntry} is a nested item that should be added
|
||||
* to the classpath. The method is called once for each entry.
|
||||
* @param jarEntry the jar entry
|
||||
* @return {@code true} if the entry is a nested item (jar or folder)
|
||||
*/
|
||||
protected abstract boolean isNestedArchive(Archive.Entry jarEntry);
|
||||
|
||||
/**
|
||||
* Called to post-process lib entries before they are used. Implementations can add
|
||||
* and remove entries.
|
||||
* @param archive the archive
|
||||
* @param lib the existing lib
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classloader for the specified lib.
|
||||
* @param lib the lib
|
||||
* @return the classloader
|
||||
* @throws Exception
|
||||
*/
|
||||
protected ClassLoader createClassLoader(List<Archive> lib) throws Exception {
|
||||
URL[] urls = new URL[lib.size()];
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
urls[i] = lib.get(i).getUrl();
|
||||
}
|
||||
return createClassLoader(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classloader for the specified URLs
|
||||
* @param urls the URLs
|
||||
* @return the classloader
|
||||
* @throws Exception
|
||||
*/
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
return new LaunchedURLClassLoader(urls, getClass().getClassLoader().getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the application given the archive file and a fully configured classloader.
|
||||
* @param args the incoming arguments
|
||||
* @param archive the archive
|
||||
* @param classLoader the classloader
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void launch(String[] args, Archive archive, ClassLoader classLoader)
|
||||
throws Exception {
|
||||
String mainClass = getMainClass(archive);
|
||||
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
|
||||
Thread runnerThread = new Thread(runner);
|
||||
runnerThread.setContextClassLoader(classLoader);
|
||||
runnerThread.setName(Thread.currentThread().getName());
|
||||
runnerThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the main class that should be used to launch the application. By default
|
||||
* this method uses a {@code Start-Class} manifest entry.
|
||||
* @param archive the archive
|
||||
* @return the main class
|
||||
* @throws Exception
|
||||
*/
|
||||
protected String getMainClass(Archive archive) throws Exception {
|
||||
String mainClass = archive.getManifest().getMainAttributes()
|
||||
.getValue("Start-Class");
|
||||
if (mainClass == null) {
|
||||
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
|
||||
}
|
||||
return mainClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@code MainMethodRunner} used to launch the application.
|
||||
* @param mainClass the main class
|
||||
* @param args the incoming arguments
|
||||
* @param classLoader the classloader
|
||||
* @return a runnable used to start the application
|
||||
* @throws Exception
|
||||
*/
|
||||
protected Runnable createMainMethodRunner(String mainClass, String[] args,
|
||||
ClassLoader classLoader) throws Exception {
|
||||
Class<?> runnerClass = classLoader.loadClass(RUNNER_CLASS);
|
||||
Constructor<?> constructor = runnerClass.getConstructor(String.class,
|
||||
String[].class);
|
||||
return (Runnable) constructor.newInstance(mainClass, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Utility class that used by {@link Launcher}s to call a main method. This class allows
|
||||
* methods to be executed within a thread configured with a specific context classloader.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MainMethodRunner implements Runnable {
|
||||
|
||||
private String mainClassName;
|
||||
|
||||
private String[] args;
|
||||
|
||||
/**
|
||||
* Create a new {@link MainMethodRunner} instance.
|
||||
* @param mainClass the main class
|
||||
* @param args incoming arguments
|
||||
*/
|
||||
public MainMethodRunner(String mainClass, String[] args) {
|
||||
this.mainClassName = mainClass;
|
||||
this.args = (args == null ? null : args.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
|
||||
.loadClass(this.mainClassName);
|
||||
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
|
||||
if (mainMethod == null) {
|
||||
throw new IllegalStateException(this.mainClassName
|
||||
+ " does not have a main method");
|
||||
}
|
||||
mainMethod.invoke(null, new Object[] { this.args });
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
|
||||
* Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided},
|
||||
* classes are loaded from {@code WEB-INF/classes}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class WarLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().equals("WEB-INF/classes/");
|
||||
}
|
||||
else {
|
||||
return entry.getName().startsWith("WEB-INF/lib/")
|
||||
|| entry.getName().startsWith("WEB-INF/lib-provided/");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
|
||||
lib.add(0, filterArchive(archive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the specified WAR file to exclude elements that should not appear on the
|
||||
* classpath.
|
||||
* @param archive the source archive
|
||||
* @return the filtered archive
|
||||
* @throws IOException on error
|
||||
*/
|
||||
protected Archive filterArchive(Archive archive) throws IOException {
|
||||
return archive.getFilteredArchive(new Archive.EntryFilter() {
|
||||
|
||||
@Override
|
||||
public String apply(String entryName, Archive.Entry entry) {
|
||||
if (entryName.startsWith("META-INF/") || entryName.startsWith("WEB-INF/")) {
|
||||
return null;
|
||||
}
|
||||
return entryName;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new WarLauncher().launch(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.data;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface that provides read-only random access to some underlying data.
|
||||
* Implementations must allow concurrent reads in a thread-safe manner.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public interface RandomAccessData {
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that can be used to read the underling data. The
|
||||
* caller is responsible close the underlying stream.
|
||||
*
|
||||
* @return a new input stream that can be used to read the underlying data.
|
||||
*/
|
||||
InputStream getInputStream();
|
||||
|
||||
/**
|
||||
* Returns a new {@link RandomAccessData} for a specific subsection of this data.
|
||||
* @param offset the offset of the subsection
|
||||
* @param length the length of the subsection
|
||||
* @return the subsection data
|
||||
*/
|
||||
RandomAccessData getSubsection(long offset, long length);
|
||||
|
||||
/**
|
||||
* Returns the size of the data.
|
||||
* @return the size
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
/**
|
||||
* {@link RandomAccessData} implementation backed by a {@link RandomAccessFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessDataFile implements RandomAccessData {
|
||||
|
||||
private static final int DEFAULT_CONCURRENT_READS = 4;
|
||||
|
||||
private File file;
|
||||
|
||||
private final FilePool filePool;
|
||||
|
||||
private final long offset;
|
||||
|
||||
private final long length;
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessDataFile} backed by the specified file.
|
||||
* @param file the underlying file
|
||||
* @throws IllegalArgumentException if the file is null or does not exist
|
||||
* @see #RandomAccessDataFile(File, int)
|
||||
*/
|
||||
public RandomAccessDataFile(File file) {
|
||||
this(file, DEFAULT_CONCURRENT_READS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessDataFile} backed by the specified file.
|
||||
* @param file the underlying file
|
||||
* @param concurrentReads the maximum number of concurrent reads allowed on the
|
||||
* underlying file before blocking
|
||||
* @throws IllegalArgumentException if the file is null or does not exist
|
||||
* @see #RandomAccessDataFile(File)
|
||||
*/
|
||||
public RandomAccessDataFile(File file, int concurrentReads) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException("File must not be null");
|
||||
}
|
||||
if (!file.exists()) {
|
||||
throw new IllegalArgumentException("File must exist");
|
||||
}
|
||||
this.file = file;
|
||||
this.filePool = new FilePool(concurrentReads);
|
||||
this.offset = 0L;
|
||||
this.length = file.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used to create a {@link #getSubsection(long, long) subsection}.
|
||||
* @param pool the underlying pool
|
||||
* @param offset the offset of the section
|
||||
* @param length the length of the section
|
||||
*/
|
||||
private RandomAccessDataFile(FilePool pool, long offset, long length) {
|
||||
this.filePool = pool;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underling File.
|
||||
* @return the underlying file
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return new DataInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomAccessData getSubsection(long offset, long length) {
|
||||
if (offset < 0 || length < 0 || offset + length > this.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new RandomAccessDataFile(this.filePool, this.offset + offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.filePool.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RandomAccessDataInputStream} implementation for the
|
||||
* {@link RandomAccessDataFile}.
|
||||
*/
|
||||
private class DataInputStream extends InputStream {
|
||||
|
||||
private long position;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return doRead(null, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b == null ? 0 : b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (b == null) {
|
||||
throw new NullPointerException("Bytes must not be null");
|
||||
}
|
||||
return doRead(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual read.
|
||||
* @param b the bytes to read or {@code null} when reading a single byte
|
||||
* @param off the offset of the byte array
|
||||
* @param len the length of data to read
|
||||
* @return the number of bytes read into {@code b} or the actual read byte if
|
||||
* {@code b} is {@code null}. Returns -1 when the end of the stream is reached
|
||||
* @throws IOException
|
||||
*/
|
||||
public int doRead(byte[] b, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (cap(len) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
RandomAccessFile file = RandomAccessDataFile.this.filePool.acquire();
|
||||
try {
|
||||
file.seek(RandomAccessDataFile.this.offset + this.position);
|
||||
if (b == null) {
|
||||
int rtn = file.read();
|
||||
moveOn(rtn == -1 ? 0 : 1);
|
||||
return rtn;
|
||||
}
|
||||
else {
|
||||
return (int) moveOn(file.read(b, off, (int) cap(len)));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
RandomAccessDataFile.this.filePool.release(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return (n <= 0 ? 0 : moveOn(cap(n)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cap the specified value such that it cannot exceed the number of bytes
|
||||
* remaining.
|
||||
* @param n the value to cap
|
||||
* @return the capped value
|
||||
*/
|
||||
private long cap(long n) {
|
||||
return Math.min(RandomAccessDataFile.this.length - this.position, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the stream position forwards the specified amount
|
||||
* @param amount the amount to move
|
||||
* @return the amount moved
|
||||
*/
|
||||
private long moveOn(long amount) {
|
||||
this.position += amount;
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage a pool that can be used to perform concurrent reads on the underlying
|
||||
* {@link RandomAccessFile}.
|
||||
*/
|
||||
private class FilePool {
|
||||
|
||||
private int size;
|
||||
|
||||
private final Semaphore available;
|
||||
|
||||
private final Queue<RandomAccessFile> files;
|
||||
|
||||
public FilePool(int size) {
|
||||
this.size = size;
|
||||
this.available = new Semaphore(size);
|
||||
this.files = new ConcurrentLinkedQueue<RandomAccessFile>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public RandomAccessFile acquire() throws IOException {
|
||||
try {
|
||||
this.available.acquire();
|
||||
RandomAccessFile file = this.files.poll();
|
||||
return (file == null ? new RandomAccessFile(
|
||||
RandomAccessDataFile.this.file, "r") : file);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void release(RandomAccessFile file) {
|
||||
this.files.add(file);
|
||||
this.available.release();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
this.available.acquire(size);
|
||||
try {
|
||||
RandomAccessFile file = files.poll();
|
||||
while (file != null) {
|
||||
file.close();
|
||||
file = files.poll();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.available.release(size);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.boot.load.data.RandomAccessData
|
||||
*/
|
||||
package org.springframework.boot.load.data;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import java.util.jar.JarEntry;
|
||||
|
||||
/**
|
||||
* Interface that can be used to filter and optionally rename jar entries.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public interface JarEntryFilter {
|
||||
|
||||
/**
|
||||
* Apply the jar entry filter.
|
||||
* @param entryName the current entry name. This may be different that the original
|
||||
* entry name if a previous filter has been applied
|
||||
* @param entry the entry to filter
|
||||
* @return the new name of the entry or {@code null} if the entry should not be
|
||||
* included.
|
||||
*/
|
||||
String apply(String entryName, JarEntry entry);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.springframework.boot.load.data.RandomAccessData;
|
||||
|
||||
/**
|
||||
* A {@link ZipEntry} returned from a {@link RandomAccessDataZipInputStream}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessDataZipEntry extends ZipEntry {
|
||||
|
||||
private RandomAccessData data;
|
||||
|
||||
/**
|
||||
* Create new {@link RandomAccessDataZipEntry} instance.
|
||||
* @param entry the underying {@link ZipEntry}
|
||||
* @param data the entry data
|
||||
*/
|
||||
public RandomAccessDataZipEntry(ZipEntry entry, RandomAccessData data) {
|
||||
super(entry);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RandomAccessData} for this entry.
|
||||
* @return the entry data
|
||||
*/
|
||||
public RandomAccessData getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.springframework.boot.load.data.RandomAccessData;
|
||||
|
||||
/**
|
||||
* A {@link ZipInputStream} backed by {@link RandomAccessData}. Parsed entries provide
|
||||
* access to the underlying data {@link RandomAccessData#getSubsection(long, long)
|
||||
* subsection}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessDataZipInputStream extends ZipInputStream {
|
||||
|
||||
private RandomAccessData data;
|
||||
|
||||
private TrackingInputStream trackingInputStream;
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessData} instance.
|
||||
* @param data the source of the zip stream
|
||||
*/
|
||||
public RandomAccessDataZipInputStream(RandomAccessData data) {
|
||||
this(data, new TrackingInputStream(data.getInputStream()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used so that we can call the super constructor with a
|
||||
* {@link TrackingInputStream}.
|
||||
* @param data the source of the zip stream
|
||||
* @param trackingInputStream a tracking input stream
|
||||
*/
|
||||
private RandomAccessDataZipInputStream(RandomAccessData data,
|
||||
TrackingInputStream trackingInputStream) {
|
||||
super(trackingInputStream);
|
||||
this.data = data;
|
||||
this.trackingInputStream = trackingInputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomAccessDataZipEntry getNextEntry() throws IOException {
|
||||
ZipEntry entry = super.getNextEntry();
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
int start = getPosition();
|
||||
closeEntry();
|
||||
int end = getPosition();
|
||||
RandomAccessData entryData = this.data.getSubsection(start, end - start);
|
||||
return new RandomAccessDataZipEntry(entry, entryData);
|
||||
}
|
||||
|
||||
private int getPosition() throws IOException {
|
||||
int pushback = ((PushbackInputStream) this.in).available();
|
||||
return this.trackingInputStream.getPosition() - pushback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal stream that tracks reads to provide a position.
|
||||
*/
|
||||
private static class TrackingInputStream extends FilterInputStream {
|
||||
|
||||
private int position = 0;
|
||||
|
||||
protected TrackingInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return moveOn(super.read(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return moveOn(super.read(b), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return moveOn(super.read(b, off, len), false);
|
||||
}
|
||||
|
||||
private int moveOn(int amount, boolean singleByteRead) {
|
||||
this.position += (amount == -1 ? 0 : (singleByteRead ? 1 : amount));
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
// Always return 0 so that we can accurately use PushbackInputStream.available
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.springframework.boot.load.data.RandomAccessData;
|
||||
import org.springframework.boot.load.data.RandomAccessDataFile;
|
||||
|
||||
/**
|
||||
* A Jar file that can loaded from a {@link RandomAccessDataFile}. This class extends and
|
||||
* behaves in the same was a the standard JDK {@link JarFile} the following additional
|
||||
* functionality.
|
||||
* <ul>
|
||||
* <li>Jar entries can be {@link JarEntryFilter filtered} during construction and new
|
||||
* filtered files can be {@link #getFilteredJarFile(JarEntryFilter...) created} from
|
||||
* existing files.</li>
|
||||
* <li>A nested {@link JarFile} can be
|
||||
* {@link #getNestedJarFile(ZipEntry, JarEntryFilter...) obtained} based on any directory
|
||||
* entry.</li>
|
||||
* <li>A nested {@link JarFile} can be
|
||||
* {@link #getNestedJarFile(ZipEntry, JarEntryFilter...) obtained} for embedded JAR files
|
||||
* (as long as their entry is not compressed).</li>
|
||||
* <li>Entry data can be accessed as {@link RandomAccessData}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessJarFile extends JarFile {
|
||||
|
||||
private final RandomAccessDataFile rootJarFile;
|
||||
|
||||
private RandomAccessData data;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final long size;
|
||||
|
||||
private Map<String, JarEntry> entries = new LinkedHashMap<String, JarEntry>();
|
||||
|
||||
private Manifest manifest;
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessJarFile} backed by the specified file.
|
||||
* @param file the root jar file
|
||||
* @param filters an optional set of jar entry filters
|
||||
* @throws IOException
|
||||
*/
|
||||
public RandomAccessJarFile(File file, JarEntryFilter... filters) throws IOException {
|
||||
this(new RandomAccessDataFile(file), filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessJarFile} backed by the specified file.
|
||||
* @param file the root jar file
|
||||
* @param filters an optional set of jar entry filters
|
||||
* @throws IOException
|
||||
*/
|
||||
public RandomAccessJarFile(RandomAccessDataFile file, JarEntryFilter... filters)
|
||||
throws IOException {
|
||||
this(file, file.getFile().getPath(), file, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used to create a new {@link RandomAccessJarFile} either
|
||||
* directly or from a nested entry.
|
||||
* @param rootJarFile the root jar file
|
||||
* @param name the name of this file
|
||||
* @param data the underlying data
|
||||
* @param filters an optional set of jar entry filters
|
||||
* @throws IOException
|
||||
*/
|
||||
private RandomAccessJarFile(RandomAccessDataFile rootJarFile, String name,
|
||||
RandomAccessData data, JarEntryFilter... filters) throws IOException {
|
||||
super(rootJarFile.getFile());
|
||||
this.rootJarFile = rootJarFile;
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
this.size = data.getSize();
|
||||
|
||||
RandomAccessDataZipInputStream inputStream = new RandomAccessDataZipInputStream(
|
||||
data);
|
||||
try {
|
||||
RandomAccessDataZipEntry zipEntry = inputStream.getNextEntry();
|
||||
while (zipEntry != null) {
|
||||
addJarEntry(zipEntry, filters);
|
||||
zipEntry = inputStream.getNextEntry();
|
||||
}
|
||||
this.manifest = findManifest();
|
||||
if (this.manifest != null) {
|
||||
for (JarEntry containedEntry : this.entries.values()) {
|
||||
((Entry) containedEntry).configure(this.manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void addJarEntry(RandomAccessDataZipEntry zipEntry, JarEntryFilter... filters) {
|
||||
Entry jarEntry = new Entry(zipEntry);
|
||||
String name = zipEntry.getName();
|
||||
for (JarEntryFilter filter : filters) {
|
||||
name = (filter == null || name == null ? name : filter.apply(name, jarEntry));
|
||||
}
|
||||
if (name != null) {
|
||||
jarEntry.setName(name);
|
||||
this.entries.put(name, jarEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private Manifest findManifest() throws IOException {
|
||||
ZipEntry manifestEntry = getEntry(MANIFEST_NAME);
|
||||
if (manifestEntry != null) {
|
||||
BufferedInputStream inputStream = new BufferedInputStream(
|
||||
getInputStream(manifestEntry));
|
||||
return new Manifest(inputStream);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final RandomAccessDataFile getRootJarFile() {
|
||||
return this.rootJarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
return this.manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<JarEntry> entries() {
|
||||
return Collections.enumeration(this.entries.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry getJarEntry(String name) {
|
||||
return (JarEntry) getEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry getEntry(String name) {
|
||||
JarEntry entry = this.entries.get(name);
|
||||
if (entry == null && name != null && !name.endsWith("/")) {
|
||||
entry = this.entries.get(name + "/");
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized InputStream getInputStream(ZipEntry ze) throws IOException {
|
||||
InputStream inputStream = getData(ze).getInputStream();
|
||||
if (ze.getMethod() == ZipEntry.DEFLATED) {
|
||||
inputStream = new ZipInflaterInputStream(inputStream);
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nested {@link RandomAccessJarFile} loaded from the specified entry.
|
||||
* @param ze the zip entry
|
||||
* @param filters an optional set of jar entry filters to be applied
|
||||
* @return a {@link RandomAccessJarFile} for the entry
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized RandomAccessJarFile getNestedJarFile(final ZipEntry ze,
|
||||
JarEntryFilter... filters) throws IOException {
|
||||
if (ze == null) {
|
||||
throw new IllegalArgumentException("ZipEntry must not be null");
|
||||
}
|
||||
|
||||
if (ze.isDirectory()) {
|
||||
return getNestedJarFileFromDirectoryEntry(ze, filters);
|
||||
}
|
||||
|
||||
return getNestedJarFileFromFileEntry(ze, filters);
|
||||
}
|
||||
|
||||
private RandomAccessJarFile getNestedJarFileFromDirectoryEntry(final ZipEntry entry,
|
||||
JarEntryFilter... filters) throws IOException {
|
||||
final String name = entry.getName();
|
||||
JarEntryFilter[] filtersToUse = new JarEntryFilter[filters.length + 1];
|
||||
System.arraycopy(filters, 0, filtersToUse, 1, filters.length);
|
||||
filtersToUse[0] = new JarEntryFilter() {
|
||||
@Override
|
||||
public String apply(String entryName, JarEntry ze) {
|
||||
if (entryName.startsWith(name) && !entryName.equals(name)) {
|
||||
return entryName.substring(entry.getName().length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return new RandomAccessJarFile(this.rootJarFile, getName() + "!/"
|
||||
+ name.substring(0, name.length() - 1), this.data, filtersToUse);
|
||||
}
|
||||
|
||||
private RandomAccessJarFile getNestedJarFileFromFileEntry(ZipEntry entry,
|
||||
JarEntryFilter... filters) throws IOException {
|
||||
if (entry.getMethod() != ZipEntry.STORED) {
|
||||
throw new IllegalStateException("Unable to open nested compressed entry "
|
||||
+ entry.getName());
|
||||
}
|
||||
return new RandomAccessJarFile(this.rootJarFile, getName() + "!/"
|
||||
+ entry.getName(), getData(entry), filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new jar based on the filtered contents of this file.
|
||||
* @param filters the set of jar entry filters to be applied
|
||||
* @return a filtered {@link RandomAccessJarFile}
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized RandomAccessJarFile getFilteredJarFile(JarEntryFilter... filters)
|
||||
throws IOException {
|
||||
return new RandomAccessJarFile(this.rootJarFile, getName(), this.data, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link RandomAccessData} for the specified entry.
|
||||
* @param ze the zip entry
|
||||
* @return the entry {@link RandomAccessData}
|
||||
* @throws IOException
|
||||
*/
|
||||
private synchronized RandomAccessData getData(ZipEntry ze) throws IOException {
|
||||
if (!this.entries.containsValue(ze)) {
|
||||
throw new IllegalArgumentException("ZipEntry must be contained in this file");
|
||||
}
|
||||
return ((Entry) ze).getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (int) this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.rootJarFile.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URL that can be used to access this JAR file. NOTE: the specified URL
|
||||
* cannot be serialized and or cloned.
|
||||
* @return the URL
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
RandomAccessJarURLStreamHandler handler = new RandomAccessJarURLStreamHandler(
|
||||
this);
|
||||
return new URL("jar", "", -1, "file:" + getName() + "!/", handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A single {@link JarEntry} in this file.
|
||||
*/
|
||||
private static class Entry extends JarEntry {
|
||||
|
||||
private String name;
|
||||
|
||||
private RandomAccessData entryData;
|
||||
|
||||
private Attributes attributes;
|
||||
|
||||
public Entry(RandomAccessDataZipEntry entry) {
|
||||
super(entry);
|
||||
this.entryData = entry.getData();
|
||||
}
|
||||
|
||||
void configure(Manifest manifest) {
|
||||
this.attributes = manifest.getAttributes(getName());
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return (this.name == null ? super.getName() : this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attributes getAttributes() throws IOException {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
public RandomAccessData getData() {
|
||||
return this.entryData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link URLStreamHandler} used to support {@link RandomAccessJarFile#getUrl()}.
|
||||
*/
|
||||
private static class RandomAccessJarURLStreamHandler extends URLStreamHandler {
|
||||
|
||||
private RandomAccessJarFile jarFile;
|
||||
|
||||
public RandomAccessJarURLStreamHandler(RandomAccessJarFile jarFile) {
|
||||
this.jarFile = jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
return new RandomAccessJarURLConnection(url, this.jarFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link JarURLConnection} used to support {@link RandomAccessJarFile#getUrl()}.
|
||||
*/
|
||||
private static class RandomAccessJarURLConnection extends JarURLConnection {
|
||||
|
||||
private RandomAccessJarFile jarFile;
|
||||
|
||||
private JarEntry jarEntry;
|
||||
|
||||
private String jarEntryName;
|
||||
|
||||
private String contentType;
|
||||
|
||||
protected RandomAccessJarURLConnection(URL url, RandomAccessJarFile jarFile)
|
||||
throws MalformedURLException {
|
||||
super(new URL("jar:file:" + jarFile.getRootJarFile().getFile().getPath()
|
||||
+ "!/"));
|
||||
this.jarFile = jarFile;
|
||||
|
||||
String spec = url.getFile();
|
||||
int separator = spec.lastIndexOf("!/");
|
||||
if (separator == -1) {
|
||||
throw new MalformedURLException("no !/ found in url spec:" + spec);
|
||||
}
|
||||
if (separator + 2 != spec.length()) {
|
||||
this.jarEntryName = spec.substring(separator + 2);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (this.jarEntryName != null) {
|
||||
this.jarEntry = this.jarFile.getJarEntry(this.jarEntryName);
|
||||
if (this.jarEntry == null) {
|
||||
throw new FileNotFoundException("JAR entry " + this.jarEntryName
|
||||
+ " not found in " + this.jarFile.getName());
|
||||
}
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomAccessJarFile getJarFile() throws IOException {
|
||||
connect();
|
||||
return this.jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry getJarEntry() throws IOException {
|
||||
connect();
|
||||
return this.jarEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
connect();
|
||||
if (this.jarEntryName == null) {
|
||||
throw new IOException("no entry name specified");
|
||||
}
|
||||
return this.jarFile.getInputStream(this.jarEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
try {
|
||||
connect();
|
||||
return (int) (this.jarEntry == null ? this.jarFile.size() : this.jarEntry
|
||||
.getSize());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() throws IOException {
|
||||
connect();
|
||||
return (this.jarEntry == null ? this.jarFile : super.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
if (this.contentType == null) {
|
||||
// Guess the content type, don't bother with steams as mark is not
|
||||
// supported
|
||||
this.contentType = (this.jarEntryName == null ? "x-java/jar" : null);
|
||||
this.contentType = (this.contentType == null ? guessContentTypeFromName(this.jarEntryName)
|
||||
: this.contentType);
|
||||
this.contentType = (this.contentType == null ? "content/unknown"
|
||||
: this.contentType);
|
||||
}
|
||||
return this.contentType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link InflaterInputStream} that support the writing of an extra "dummy" byte which
|
||||
* is required with JDK 6
|
||||
*/
|
||||
private static class ZipInflaterInputStream extends InflaterInputStream {
|
||||
|
||||
private boolean extraBytesWritten;
|
||||
|
||||
public ZipInflaterInputStream(InputStream inputStream) {
|
||||
super(inputStream, new Inflater(true), 512);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fill() throws IOException {
|
||||
try {
|
||||
super.fill();
|
||||
}
|
||||
catch (EOFException ex) {
|
||||
if (this.extraBytesWritten) {
|
||||
throw ex;
|
||||
}
|
||||
this.len = 1;
|
||||
this.buf[0] = 0x0;
|
||||
this.extraBytesWritten = true;
|
||||
this.inf.setInput(this.buf, 0, this.len);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for loading and manipulating JAR/WAR files.
|
||||
*/
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* System that allows self contained JAR/WAR archives to be launched using
|
||||
* {@code java -jar}. Archives can include nested packaged dependency JARs (there is
|
||||
* no need to create shade style jars) and are executed without unpacking. The only
|
||||
* constraint is that nested JARs must be stored in the archive uncompressed.
|
||||
*
|
||||
* @see org.springframework.boot.load.JarLauncher
|
||||
* @see org.springframework.boot.load.WarLauncher
|
||||
*/
|
||||
package org.springframework.boot.load;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
/**
|
||||
* Hamcrest matcher to tests that a byte array starts with specific bytes.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ByteArrayStartsWith extends TypeSafeMatcher<byte[]> {
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public ByteArrayStartsWith(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a byte array starting with ").appendValue(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(byte[] item) {
|
||||
if (item.length < bytes.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
if (item[i] != bytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.load.Archive;
|
||||
import org.springframework.boot.load.ExplodedArchive;
|
||||
import org.springframework.boot.load.Archive.Entry;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExplodedArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ExplodedArchiveTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File rootFolder;
|
||||
|
||||
private ExplodedArchive archive;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
TestJarCreator.createTestJar(file);
|
||||
|
||||
this.rootFolder = this.temporaryFolder.newFolder();
|
||||
JarFile jarFile = new JarFile(file);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
File destination = new File(this.rootFolder.getAbsolutePath()
|
||||
+ File.separator + entry.getName());
|
||||
destination.getParentFile().mkdirs();
|
||||
if (entry.isDirectory()) {
|
||||
destination.mkdir();
|
||||
}
|
||||
else {
|
||||
copy(jarFile.getInputStream(entry), new FileOutputStream(destination));
|
||||
}
|
||||
}
|
||||
this.archive = new ExplodedArchive(this.rootFolder);
|
||||
}
|
||||
|
||||
private void copy(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = in.read(buffer);
|
||||
while (len != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
len = in.read(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By"),
|
||||
equalTo("j1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntries() throws Exception {
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
|
||||
assertThat(entries.size(), equalTo(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrl() throws Exception {
|
||||
URL url = this.archive.getUrl();
|
||||
assertThat(url, equalTo(this.rootFolder.toURI().toURL()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNestedArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
Archive nested = this.archive.getNestedArchive(entry);
|
||||
assertThat(nested.getUrl().toString(),
|
||||
equalTo("jar:file:" + this.rootFolder.getPath() + "/nested.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedDirArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("d/");
|
||||
Archive nested = this.archive.getNestedArchive(entry);
|
||||
Map<String, Entry> nestedEntries = getEntriesMap(nested);
|
||||
assertThat(nestedEntries.size(), equalTo(1));
|
||||
assertThat(nested.getUrl().toString(),
|
||||
equalTo("file:" + this.rootFolder.getPath() + "/d/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFilteredArchive() throws Exception {
|
||||
Archive filteredArchive = this.archive
|
||||
.getFilteredArchive(new Archive.EntryFilter() {
|
||||
@Override
|
||||
public String apply(String entryName, Entry entry) {
|
||||
if (entryName.equals("1.dat")) {
|
||||
return entryName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Map<String, Entry> entries = getEntriesMap(filteredArchive);
|
||||
assertThat(entries.size(), equalTo(1));
|
||||
URLClassLoader classLoader = new URLClassLoader(
|
||||
new URL[] { filteredArchive.getUrl() });
|
||||
assertThat(classLoader.getResourceAsStream("1.dat").read(), equalTo(1));
|
||||
assertThat(classLoader.getResourceAsStream("2.dat"), nullValue());
|
||||
}
|
||||
|
||||
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
|
||||
for (Archive.Entry entry : archive.getEntries()) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.load.Archive;
|
||||
import org.springframework.boot.load.JarFileArchive;
|
||||
import org.springframework.boot.load.Archive.Entry;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JarFileArchive}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class JarFileArchiveTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File rootJarFile;
|
||||
|
||||
private JarFileArchive archive;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.rootJarFile = this.temporaryFolder.newFile();
|
||||
TestJarCreator.createTestJar(this.rootJarFile);
|
||||
this.archive = new JarFileArchive(this.rootJarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getManifest() throws Exception {
|
||||
assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By"),
|
||||
equalTo("j1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntries() throws Exception {
|
||||
Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
|
||||
assertThat(entries.size(), equalTo(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrl() throws Exception {
|
||||
URL url = this.archive.getUrl();
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNestedArchive() throws Exception {
|
||||
Entry entry = getEntriesMap(this.archive).get("nested.jar");
|
||||
Archive nested = this.archive.getNestedArchive(entry);
|
||||
assertThat(nested.getUrl().toString(),
|
||||
equalTo("jar:file:" + this.rootJarFile.getPath() + "!/nested.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFilteredArchive() throws Exception {
|
||||
Archive filteredArchive = this.archive
|
||||
.getFilteredArchive(new Archive.EntryFilter() {
|
||||
@Override
|
||||
public String apply(String entryName, Entry entry) {
|
||||
if (entryName.equals("1.dat")) {
|
||||
return entryName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Map<String, Entry> entries = getEntriesMap(filteredArchive);
|
||||
assertThat(entries.size(), equalTo(1));
|
||||
}
|
||||
|
||||
private Map<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<String, Archive.Entry>();
|
||||
for (Archive.Entry entry : archive.getEntries()) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.boot.load;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* Creates a simple test jar.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public abstract class TestJarCreator {
|
||||
|
||||
public static void createTestJar(File file) throws Exception {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream);
|
||||
try {
|
||||
writeManifest(jarOutputStream, "j1");
|
||||
writeEntry(jarOutputStream, "1.dat", 1);
|
||||
writeEntry(jarOutputStream, "2.dat", 2);
|
||||
writeDirEntry(jarOutputStream, "d/");
|
||||
writeEntry(jarOutputStream, "d/9.dat", 9);
|
||||
|
||||
JarEntry nestedEntry = new JarEntry("nested.jar");
|
||||
byte[] nestedJarData = getNestedJarData();
|
||||
nestedEntry.setSize(nestedJarData.length);
|
||||
nestedEntry.setCompressedSize(nestedJarData.length);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(nestedJarData);
|
||||
nestedEntry.setCrc(crc32.getValue());
|
||||
|
||||
nestedEntry.setMethod(ZipEntry.STORED);
|
||||
jarOutputStream.putNextEntry(nestedEntry);
|
||||
jarOutputStream.write(nestedJarData);
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
finally {
|
||||
jarOutputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getNestedJarData() throws Exception {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream);
|
||||
writeManifest(jarOutputStream, "j2");
|
||||
writeEntry(jarOutputStream, "3.dat", 3);
|
||||
writeEntry(jarOutputStream, "4.dat", 4);
|
||||
jarOutputStream.close();
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
private static void writeManifest(JarOutputStream jarOutputStream, String name)
|
||||
throws Exception {
|
||||
writeDirEntry(jarOutputStream, "META-INF/");
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().putValue("Built-By", name);
|
||||
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
jarOutputStream.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
|
||||
manifest.write(jarOutputStream);
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
private static void writeDirEntry(JarOutputStream jarOutputStream, String name)
|
||||
throws IOException {
|
||||
jarOutputStream.putNextEntry(new JarEntry(name));
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
private static void writeEntry(JarOutputStream jarOutputStream, String name, int data)
|
||||
throws IOException {
|
||||
jarOutputStream.putNextEntry(new JarEntry(name));
|
||||
jarOutputStream.write(new byte[] { (byte) data });
|
||||
jarOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.data;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.load.ByteArrayStartsWith;
|
||||
import org.springframework.boot.load.data.RandomAccessData;
|
||||
import org.springframework.boot.load.data.RandomAccessDataFile;
|
||||
|
||||
/**
|
||||
* Tests for {@link RandomAccessDataFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessDataFileTests {
|
||||
|
||||
private static final byte[] BYTES;
|
||||
static {
|
||||
BYTES = new byte[256];
|
||||
for (int i = 0; i < BYTES.length; i++) {
|
||||
BYTES[i] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File tempFile;
|
||||
|
||||
private RandomAccessDataFile file;
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.tempFile = temporaryFolder.newFile();
|
||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
||||
outputStream.write(BYTES);
|
||||
outputStream.close();
|
||||
this.file = new RandomAccessDataFile(tempFile);
|
||||
this.inputStream = file.getInputStream();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() throws Exception {
|
||||
inputStream.close();
|
||||
file.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileNotNull() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.equals("File must not be null");
|
||||
new RandomAccessDataFile(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileExists() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.equals("File must exist");
|
||||
new RandomAccessDataFile(new File("/does/not/exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileNotNullWithConcurrentReads() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.equals("File must not be null");
|
||||
new RandomAccessDataFile(null, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileExistsWithConcurrentReads() throws Exception {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.equals("File must exist");
|
||||
new RandomAccessDataFile(new File("/does/not/exist"), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamRead() throws Exception {
|
||||
for (int i = 0; i <= 255; i++) {
|
||||
assertThat(inputStream.read(), equalTo(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadNullBytes() throws Exception {
|
||||
thrown.expect(NullPointerException.class);
|
||||
thrown.expectMessage("Bytes must not be null");
|
||||
inputStream.read(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intputStreamReadNullBytesWithOffset() throws Exception {
|
||||
thrown.expect(NullPointerException.class);
|
||||
thrown.expectMessage("Bytes must not be null");
|
||||
inputStream.read(null, 0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadBytes() throws Exception {
|
||||
byte[] b = new byte[256];
|
||||
int amountRead = inputStream.read(b);
|
||||
assertThat(b, equalTo(BYTES));
|
||||
assertThat(amountRead, equalTo(256));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputSteamReadOffsetBytes() throws Exception {
|
||||
byte[] b = new byte[7];
|
||||
inputStream.skip(1);
|
||||
int amountRead = inputStream.read(b, 2, 3);
|
||||
assertThat(b, equalTo(new byte[] { 0, 0, 1, 2, 3, 0, 0 }));
|
||||
assertThat(amountRead, equalTo(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadMoreBytesThanAvailable() throws Exception {
|
||||
byte[] b = new byte[257];
|
||||
int amountRead = inputStream.read(b);
|
||||
assertThat(b, startsWith(BYTES));
|
||||
assertThat(amountRead, equalTo(256));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadPastEnd() throws Exception {
|
||||
inputStream.skip(255);
|
||||
assertThat(inputStream.read(), equalTo(0xFF));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadZeroLength() throws Exception {
|
||||
byte[] b = new byte[] { 0x0F };
|
||||
int amountRead = inputStream.read(b, 0, 0);
|
||||
assertThat(b, equalTo(new byte[] { 0x0F }));
|
||||
assertThat(amountRead, equalTo(0));
|
||||
assertThat(inputStream.read(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamSkip() throws Exception {
|
||||
long amountSkipped = inputStream.skip(4);
|
||||
assertThat(inputStream.read(), equalTo(4));
|
||||
assertThat(amountSkipped, equalTo(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamSkipMoreThanAvailable() throws Exception {
|
||||
long amountSkipped = inputStream.skip(257);
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
assertThat(amountSkipped, equalTo(256L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamSkipPastEnd() throws Exception {
|
||||
inputStream.skip(256);
|
||||
long amountSkipped = inputStream.skip(1);
|
||||
assertThat(amountSkipped, equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionNegativeOffset() throws Exception {
|
||||
thrown.expect(IndexOutOfBoundsException.class);
|
||||
file.getSubsection(-1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionNegativeLength() throws Exception {
|
||||
thrown.expect(IndexOutOfBoundsException.class);
|
||||
file.getSubsection(0, -1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionZeroLength() throws Exception {
|
||||
RandomAccessData subsection = file.getSubsection(0, 0);
|
||||
assertThat(subsection.getInputStream().read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionTooBig() throws Exception {
|
||||
file.getSubsection(0, 256);
|
||||
thrown.expect(IndexOutOfBoundsException.class);
|
||||
file.getSubsection(0, 257);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsectionTooBigWithOffset() throws Exception {
|
||||
file.getSubsection(1, 255);
|
||||
thrown.expect(IndexOutOfBoundsException.class);
|
||||
file.getSubsection(1, 256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subsection() throws Exception {
|
||||
RandomAccessData subsection = file.getSubsection(1, 1);
|
||||
assertThat(subsection.getInputStream().read(), equalTo(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
assertThat(inputStream.read(), equalTo(1));
|
||||
assertThat(inputStream.read(), equalTo(2));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamReadBytesPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
byte[] b = new byte[3];
|
||||
int amountRead = inputStream.read(b);
|
||||
assertThat(b, equalTo(new byte[] { 1, 2, 0 }));
|
||||
assertThat(amountRead, equalTo(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamSkipPastSubsection() throws Exception {
|
||||
RandomAccessData subsection = file.getSubsection(1, 2);
|
||||
InputStream inputStream = subsection.getInputStream();
|
||||
assertThat(inputStream.skip(3), equalTo(2L));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputStreamSkipNegative() throws Exception {
|
||||
assertThat(inputStream.skip(-1), equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFile() throws Exception {
|
||||
assertThat(file.getFile(), equalTo(tempFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentReads() throws Exception {
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(20);
|
||||
List<Future<Boolean>> results = new ArrayList<Future<Boolean>>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
results.add(executorService.submit(new Callable<Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
InputStream subsectionInputStream = file.getSubsection(0, 256)
|
||||
.getInputStream();
|
||||
byte[] b = new byte[256];
|
||||
subsectionInputStream.read(b);
|
||||
return Arrays.equals(b, BYTES);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for (Future<Boolean> future : results) {
|
||||
assertThat(future.get(), equalTo(true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void close() throws Exception {
|
||||
file.getInputStream().read();
|
||||
file.close();
|
||||
Field filePoolField = RandomAccessDataFile.class.getDeclaredField("filePool");
|
||||
filePoolField.setAccessible(true);
|
||||
Object filePool = filePoolField.get(file);
|
||||
Field filesField = filePool.getClass().getDeclaredField("files");
|
||||
filesField.setAccessible(true);
|
||||
Queue<?> queue = (Queue<?>) filesField.get(filePool);
|
||||
assertThat(queue.size(), equalTo(0));
|
||||
}
|
||||
|
||||
private static Matcher<? super byte[]> startsWith(byte[] bytes) {
|
||||
return new ByteArrayStartsWith(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.load.data.RandomAccessDataFile;
|
||||
import org.springframework.boot.load.jar.RandomAccessDataZipEntry;
|
||||
import org.springframework.boot.load.jar.RandomAccessDataZipInputStream;
|
||||
|
||||
/**
|
||||
* Tests for {@link RandomAccessDataZipInputStream}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessDataZipInputStreamTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File file;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.file = temporaryFolder.newFile();
|
||||
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(file));
|
||||
try {
|
||||
writeDataEntry(zipOutputStream, "a", new byte[10]);
|
||||
writeDataEntry(zipOutputStream, "b", new byte[20]);
|
||||
}
|
||||
finally {
|
||||
zipOutputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDataEntry(ZipOutputStream zipOutputStream, String name, byte[] data)
|
||||
throws IOException {
|
||||
ZipEntry entry = new ZipEntry(name);
|
||||
entry.setMethod(ZipEntry.STORED);
|
||||
entry.setSize(data.length);
|
||||
entry.setCompressedSize(data.length);
|
||||
CRC32 crc32 = new CRC32();
|
||||
crc32.update(data);
|
||||
entry.setCrc(crc32.getValue());
|
||||
zipOutputStream.putNextEntry(entry);
|
||||
zipOutputStream.write(data);
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryData() throws Exception {
|
||||
RandomAccessDataZipInputStream z = new RandomAccessDataZipInputStream(
|
||||
new RandomAccessDataFile(file));
|
||||
try {
|
||||
RandomAccessDataZipEntry entry1 = z.getNextEntry();
|
||||
RandomAccessDataZipEntry entry2 = z.getNextEntry();
|
||||
assertThat(entry1.getName(), equalTo("a"));
|
||||
assertThat(entry1.getData().getSize(), equalTo(10L));
|
||||
assertThat(entry2.getName(), equalTo("b"));
|
||||
assertThat(entry2.getData().getSize(), equalTo(20L));
|
||||
assertThat(z.getNextEntry(), nullValue());
|
||||
}
|
||||
finally {
|
||||
z.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.load.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.load.TestJarCreator;
|
||||
import org.springframework.boot.load.data.RandomAccessDataFile;
|
||||
import org.springframework.boot.load.jar.JarEntryFilter;
|
||||
import org.springframework.boot.load.jar.RandomAccessJarFile;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link RandomAccessJarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class RandomAccessJarFileTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File rootJarFile;
|
||||
|
||||
private RandomAccessJarFile jarFile;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.rootJarFile = this.temporaryFolder.newFile();
|
||||
TestJarCreator.createTestJar(this.rootJarFile);
|
||||
this.jarFile = new RandomAccessJarFile(this.rootJarFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromFile() throws Exception {
|
||||
RandomAccessJarFile jarFile = new RandomAccessJarFile(this.rootJarFile);
|
||||
assertThat(jarFile.getName(), notNullValue(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromRandomAccessDataFile() throws Exception {
|
||||
RandomAccessDataFile randomAccessDataFile = new RandomAccessDataFile(
|
||||
this.rootJarFile, 1);
|
||||
RandomAccessJarFile jarFile = new RandomAccessJarFile(randomAccessDataFile);
|
||||
assertThat(jarFile.getName(), notNullValue(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getManifest() throws Exception {
|
||||
assertThat(this.jarFile.getManifest().getMainAttributes().getValue("Built-By"),
|
||||
equalTo("j1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntries() throws Exception {
|
||||
Enumeration<JarEntry> entries = this.jarFile.entries();
|
||||
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("1.dat"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("2.dat"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("d/"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("d/9.dat"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("nested.jar"));
|
||||
assertThat(entries.hasMoreElements(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getJarEntry() throws Exception {
|
||||
JarEntry entry = this.jarFile.getJarEntry("1.dat");
|
||||
assertThat(entry, notNullValue(ZipEntry.class));
|
||||
assertThat(entry.getName(), equalTo("1.dat"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStream() throws Exception {
|
||||
InputStream inputStream = this.jarFile.getInputStream(this.jarFile
|
||||
.getEntry("1.dat"));
|
||||
assertThat(inputStream.read(), equalTo(1));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getName() throws Exception {
|
||||
assertThat(this.jarFile.getName(), equalTo(this.rootJarFile.getPath()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSize() throws Exception {
|
||||
assertThat(this.jarFile.size(), equalTo((int) this.rootJarFile.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void close() throws Exception {
|
||||
RandomAccessDataFile randomAccessDataFile = spy(new RandomAccessDataFile(
|
||||
this.rootJarFile, 1));
|
||||
RandomAccessJarFile jarFile = new RandomAccessJarFile(randomAccessDataFile);
|
||||
jarFile.close();
|
||||
verify(randomAccessDataFile).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrl() throws Exception {
|
||||
URL url = this.jarFile.getUrl();
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/"));
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
|
||||
assertThat(jarURLConnection.getJarEntry(), nullValue());
|
||||
assertThat(jarURLConnection.getContentLength(), greaterThan(1));
|
||||
assertThat(jarURLConnection.getContent(), sameInstance((Object) this.jarFile));
|
||||
assertThat(jarURLConnection.getContentType(), equalTo("x-java/jar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntryUrl() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "1.dat");
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/1.dat"));
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
|
||||
assertThat(jarURLConnection.getJarEntry(),
|
||||
sameInstance(this.jarFile.getJarEntry("1.dat")));
|
||||
assertThat(jarURLConnection.getContentLength(), equalTo(1));
|
||||
assertThat(jarURLConnection.getContent(), instanceOf(InputStream.class));
|
||||
assertThat(jarURLConnection.getContentType(), equalTo("content/unknown"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMissingEntryUrl() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "missing.dat");
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/missing.dat"));
|
||||
this.thrown.expect(FileNotFoundException.class);
|
||||
((JarURLConnection) url.openConnection()).getJarEntry();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrlStream() throws Exception {
|
||||
URL url = this.jarFile.getUrl();
|
||||
url.openConnection();
|
||||
this.thrown.expect(IOException.class);
|
||||
url.openStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getEntryUrlStream() throws Exception {
|
||||
URL url = new URL(this.jarFile.getUrl(), "1.dat");
|
||||
url.openConnection();
|
||||
InputStream stream = url.openStream();
|
||||
assertThat(stream.read(), equalTo(1));
|
||||
assertThat(stream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNestedJarFile() throws Exception {
|
||||
RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
|
||||
.getEntry("nested.jar"));
|
||||
|
||||
Enumeration<JarEntry> entries = nestedJarFile.entries();
|
||||
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("3.dat"));
|
||||
assertThat(entries.nextElement().getName(), equalTo("4.dat"));
|
||||
assertThat(entries.hasMoreElements(), equalTo(false));
|
||||
|
||||
InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile
|
||||
.getEntry("3.dat"));
|
||||
assertThat(inputStream.read(), equalTo(3));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
|
||||
URL url = nestedJarFile.getUrl();
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/nested.jar!/"));
|
||||
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
|
||||
sameInstance((JarFile) nestedJarFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNestedJarDirectory() throws Exception {
|
||||
RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
|
||||
.getEntry("d/"));
|
||||
|
||||
Enumeration<JarEntry> entries = nestedJarFile.entries();
|
||||
assertThat(entries.nextElement().getName(), equalTo("9.dat"));
|
||||
assertThat(entries.hasMoreElements(), equalTo(false));
|
||||
|
||||
InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile
|
||||
.getEntry("9.dat"));
|
||||
assertThat(inputStream.read(), equalTo(9));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
|
||||
URL url = nestedJarFile.getUrl();
|
||||
assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
|
||||
+ "!/d!/"));
|
||||
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
|
||||
sameInstance((JarFile) nestedJarFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDirectoryInputStream() throws Exception {
|
||||
InputStream inputStream = this.jarFile
|
||||
.getInputStream(this.jarFile.getEntry("d/"));
|
||||
assertThat(inputStream, notNullValue());
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDirectoryInputStreamWithoutSlash() throws Exception {
|
||||
InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d"));
|
||||
assertThat(inputStream, notNullValue());
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFilteredJarFile() throws Exception {
|
||||
RandomAccessJarFile filteredJarFile = this.jarFile
|
||||
.getFilteredJarFile(new JarEntryFilter() {
|
||||
@Override
|
||||
public String apply(String entryName, JarEntry entry) {
|
||||
if (entryName.equals("1.dat")) {
|
||||
return "x.dat";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Enumeration<JarEntry> entries = filteredJarFile.entries();
|
||||
assertThat(entries.nextElement().getName(), equalTo("x.dat"));
|
||||
assertThat(entries.hasMoreElements(), equalTo(false));
|
||||
|
||||
InputStream inputStream = filteredJarFile.getInputStream(filteredJarFile
|
||||
.getEntry("x.dat"));
|
||||
assertThat(inputStream.read(), equalTo(1));
|
||||
assertThat(inputStream.read(), equalTo(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sensibleToString() throws Exception {
|
||||
assertThat(this.jarFile.toString(), equalTo(this.rootJarFile.getPath()));
|
||||
assertThat(this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))
|
||||
.toString(), equalTo(this.rootJarFile.getPath() + "!/nested.jar"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user