Commit ba2ea301 authored by Phillip Webb's avatar Phillip Webb

Merge branch 'gh-1117'

parents ffc5d565 ed57adb5
...@@ -383,6 +383,18 @@ want the other Boot features but not this one) ...@@ -383,6 +383,18 @@ want the other Boot features but not this one)
|`customConfiguration` |`customConfiguration`
|The name of the custom configuration which is used to populate the nested lib directory |The name of the custom configuration which is used to populate the nested lib directory
(without specifying this you get all compile and runtime dependencies). (without specifying this you get all compile and runtime dependencies).
|`executable`
|Boolean flag to indicate if jar files are fully executable on Unix like operating
systems. Defaults to `true`.
|`embeddedLaunchScript`
|The embedded launch script to prepend to the front of the jar if it is fully executable.
If not specified the 'Spring Boot' default script will be used.
|`embeddedLaunchScriptProperties`
|Additional properties that to be expanded in the launch script. The default script
supports a `mode` property which can contain the values `auto`, `service` or `run`.
|=== |===
......
[[cloud-deployment]] [[deployment]]
= Deploying to the cloud == Deploying Spring Boot applications
[partintro] [partintro]
-- --
Spring Boot's flexible packaging options provide a great deal of choice when it comes to
deploying your application. You can easily deploy Spring Boot applications to a variety
of cloud platforms, to a container images (such as Docker) or to virtual/real machines.
This section covers some of the more common deployment scenarios.
--
[[cloud-deployment]]
== Deploying to the cloud
Spring Boot's executable jars are ready-made for most popular cloud PaaS Spring Boot's executable jars are ready-made for most popular cloud PaaS
(platform-as-a-service) providers. These providers tend to require that you (platform-as-a-service) providers. These providers tend to require that you
"`bring your own container`"; they manage application processes (not Java applications "`bring your own container`"; they manage application processes (not Java applications
...@@ -23,12 +35,11 @@ to run packaged within it. ...@@ -23,12 +35,11 @@ to run packaged within it.
In this section we'll look at what it takes to get the In this section we'll look at what it takes to get the
<<getting-started.adoc#getting-started-first-application, simple application that we <<getting-started.adoc#getting-started-first-application, simple application that we
developed>> in the "`Getting Started`" section up and running in the Cloud. developed>> in the "`Getting Started`" section up and running in the Cloud.
--
[[cloud-deployment-cloud-foundry]] [[cloud-deployment-cloud-foundry]]
== Cloud Foundry === Cloud Foundry
Cloud Foundry provides default buildpacks that come into play if no other buildpack is Cloud Foundry provides default buildpacks that come into play if no other buildpack is
specified. The Cloud Foundry https://github.com/cloudfoundry/java-buildpack[Java buildpack] specified. The Cloud Foundry https://github.com/cloudfoundry/java-buildpack[Java buildpack]
has excellent support for Spring applications, including Spring Boot. You can deploy has excellent support for Spring applications, including Spring Boot. You can deploy
...@@ -102,7 +113,7 @@ able to hit the application at the URI given, in this case ...@@ -102,7 +113,7 @@ able to hit the application at the URI given, in this case
[[cloud-deployment-cloud-foundry-services]] [[cloud-deployment-cloud-foundry-services]]
=== Binding to services ==== Binding to services
By default, metadata about the running application as well as service connection By default, metadata about the running application as well as service connection
information is exposed to the application as environment variables (for example: information is exposed to the application as environment variables (for example:
`$VCAP_SERVICES`). This architecture decision is due to Cloud Foundry's polyglot `$VCAP_SERVICES`). This architecture decision is due to Cloud Foundry's polyglot
...@@ -142,7 +153,7 @@ auto-configuration support and a `spring-boot-starter-cloud-connectors` starter ...@@ -142,7 +153,7 @@ auto-configuration support and a `spring-boot-starter-cloud-connectors` starter
[[cloud-deployment-heroku]] [[cloud-deployment-heroku]]
== Heroku === Heroku
Heroku is another popular PaaS platform. To customize Heroku builds, you provide a Heroku is another popular PaaS platform. To customize Heroku builds, you provide a
`Procfile`, which provides the incantation required to deploy an application. Heroku `Procfile`, which provides the incantation required to deploy an application. Heroku
assigns a `port` for the Java application to use and then ensures that routing to the assigns a `port` for the Java application to use and then ensures that routing to the
...@@ -225,7 +236,7 @@ Your application should now be up and running on Heroku. ...@@ -225,7 +236,7 @@ Your application should now be up and running on Heroku.
[[cloud-deployment-openshift]] [[cloud-deployment-openshift]]
== Openshift === Openshift
https://www.openshift.com/[Openshift] is the RedHat public (and enterprise) PaaS solution. https://www.openshift.com/[Openshift] is the RedHat public (and enterprise) PaaS solution.
Like Heroku, it works by running scripts triggered by git commits, so you can script Like Heroku, it works by running scripts triggered by git commits, so you can script
the launching of a Spring Boot application in pretty much any way you like as long as the the launching of a Spring Boot application in pretty much any way you like as long as the
...@@ -288,14 +299,85 @@ run the app. ...@@ -288,14 +299,85 @@ run the app.
[[cloud-deployment-gae]] [[cloud-deployment-gae]]
== Google App Engine === Google App Engine
Google App Engine is tied to the Servlet 2.5 API, so you can't deploy a Spring Application Google App Engine is tied to the Servlet 2.5 API, so you can't deploy a Spring Application
there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>> there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>>
of this guide. of this guide.
[[deployment-service]]
== Installing Spring Boot applications
In additional to running Spring Boot applications using `java -jar` it is also possible
to execute applications directly on Unix systems (Linux, OSX, FreeBSD etc). This makes it
very easy to install and manage Spring Boot applications in common production
environments. As long as you are generating '`fully executable`' jars from your build, and
you are not using a custom `embeddedLaunchScript`, the following techniques can be used.
=== Unix/Linux services
Spring Boot application can be easily started as Unix/Linux services using either `init.d`
or `systemd`.
==== Installation as a init.d (system v) service
The default executable script that is embedded into Spring Boot executable jars will act
as an `init.d` script when it is symlinked to `/etc/init.d`. The standard `start`, `stop`,
`restart` and `status` commands can be used. The script supports the following features:
* Starts the services as the user that owns the jar file
* Tracks application PIDs using `/var/run/<appname>.pid`
* Writes console logs to `/var/log/<appname>.log`
Assuming that you have a Spring Boot application installed in `/var/myapp`, to install a
Spring Boot application as an `init.d` service simply create a symlink:
[indent=0,subs="verbatim,quotes,attributes"]
----
$ sudo link -s /var/myapp/myapp.jar /etc/init.d/myapp
----
TIP: It is advisable to create a specific user account to run you application. Ensure
that you have set the owner of the jar file using `chown` before installing your service.
[[cloud-deployment-whats-next]] Once installed, you can start and stop the service in the usual way. You can also flag the
application to start automatically using your standard operating system tools. For example,
if you use Debian:
[indent=0,subs="verbatim,quotes,attributes"]
----
$ update-rc.d myapp defaults <priority>
----
==== Installation as a systemd service
Systemd is the successor to `init.d` scripts, and now being used by many many modern Linux
distributions. Although you can continue to use `init.d` script with `systemd`, it is also
possible to launch Spring Boot applications using `systemd` '`service`' scripts.
For example, to run a Spring Boot application installed in `var/myapp` you can add the
following script in `/etc/systemd/system/myapp.service`:
[indent=0]
----
[Unit]
Description=myapp
After=syslog.target
[Service]
ExecStart=/var/myapp/myapp.jar
[Install]
WantedBy=multi-user.target
----
TIP: Remember to change the `Description` and `ExecStart` fields for your application.
[[deployment-whats-next]]
== What to read next == What to read next
Check out the http://www.cloudfoundry.com/[Cloud Foundry], https://www.heroku.com/[Heroku] Check out the http://www.cloudfoundry.com/[Cloud Foundry], https://www.heroku.com/[Heroku]
and https://www.openshift.com[Openshift] web sites for more information about the kinds of and https://www.openshift.com[Openshift] web sites for more information about the kinds of
...@@ -307,6 +389,3 @@ The next section goes on to cover the _<<spring-boot-cli.adoc#cli, Spring Boot C ...@@ -307,6 +389,3 @@ The next section goes on to cover the _<<spring-boot-cli.adoc#cli, Spring Boot C
or you can jump ahead to read about or you can jump ahead to read about
_<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_. _<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_.
...@@ -136,10 +136,9 @@ When you're ready to push your Spring Boot application to production, we've got ...@@ -136,10 +136,9 @@ When you're ready to push your Spring Boot application to production, we've got
== Advanced topics == Advanced topics
Lastly, we have a few topics for the more advanced user. Lastly, we have a few topics for the more advanced user.
* *Deploy to the cloud:* * *Deploy Spring Boot Applications:*
<<cloud-deployment.adoc#cloud-deployment-cloud-foundry, Cloud Foundry>> | <<deployment.adoc#cloud-deployment, Cloud Deployment>> |
<<cloud-deployment.adoc#cloud-deployment-heroku, Heroku>> | <<deployment.adoc#deployment-service, OS Service>>
<<cloud-deployment.adoc#cloud-deployment-cloudbees, CloudBees>>
* *Build tool plugins:* * *Build tool plugins:*
<<build-tool-plugins.adoc#build-tool-plugins-maven-plugin, Maven>> | <<build-tool-plugins.adoc#build-tool-plugins-maven-plugin, Maven>> |
<<build-tool-plugins.adoc#build-tool-plugins-gradle-plugin, Gradle>> <<build-tool-plugins.adoc#build-tool-plugins-gradle-plugin, Gradle>>
......
...@@ -45,7 +45,7 @@ include::getting-started.adoc[] ...@@ -45,7 +45,7 @@ include::getting-started.adoc[]
include::using-spring-boot.adoc[] include::using-spring-boot.adoc[]
include::spring-boot-features.adoc[] include::spring-boot-features.adoc[]
include::production-ready-features.adoc[] include::production-ready-features.adoc[]
include::cloud-deployment.adoc[] include::deployment.adoc[]
include::spring-boot-cli.adoc[] include::spring-boot-cli.adoc[]
include::build-tool-plugins.adoc[] include::build-tool-plugins.adoc[]
include::howto.adoc[] include::howto.adoc[]
......
...@@ -1043,7 +1043,7 @@ If you want to explore some of the concepts discussed in this chapter, you can t ...@@ -1043,7 +1043,7 @@ If you want to explore some of the concepts discussed in this chapter, you can t
look at the actuator {github-code}/spring-boot-samples[sample applications]. You also look at the actuator {github-code}/spring-boot-samples[sample applications]. You also
might want to read about graphing tools such as http://graphite.wikidot.com/[Graphite]. might want to read about graphing tools such as http://graphite.wikidot.com/[Graphite].
Otherwise, you can continue on, to read about <<cloud-deployment.adoc#cloud-deployment, Otherwise, you can continue on, to read about <<deployment.adoc#deployment,
'`cloud deployment options`'>> or jump ahead '`deployment options`'>> or jump ahead
for some in-depth information about Spring Boot's for some in-depth information about Spring Boot's
_<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_. _<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_.
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
[partintro] [partintro]
-- --
This section goes into more detail about how you should use Spring Boot. It covers topics This section goes into more detail about how you should use Spring Boot. It covers topics
such as build systems, auto-configuration and run/deployment options. We also cover some such as build systems, auto-configuration and how to run your applications. We also cover
Spring Boot best practices. Although there is nothing particularly special about some Spring Boot best practices. Although there is nothing particularly special about
Spring Boot (it is just another library that you can consume), there are a few Spring Boot (it is just another library that you can consume), there are a few
recommendations that, when followed, will make your development process just a recommendations that, when followed, will make your development process just a
little easier. little easier.
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package org.springframework.boot.gradle package org.springframework.boot.gradle
import java.io.File;
import java.util.Map;
import org.springframework.boot.loader.tools.Layout import org.springframework.boot.loader.tools.Layout
import org.springframework.boot.loader.tools.Layouts import org.springframework.boot.loader.tools.Layouts
...@@ -130,4 +133,21 @@ public class SpringBootPluginExtension { ...@@ -130,4 +133,21 @@ public class SpringBootPluginExtension {
*/ */
boolean applyExcludeRules = true; boolean applyExcludeRules = true;
/**
* If a fully executable jar (for *nix machines) should be generated by prepending a
* launch script to the jar.
*/
boolean executable = true;
/**
* The embedded launch script to prepend to the front of the jar if it is fully
* executable. If not specified the 'Spring Boot' default script will be used.
*/
File embeddedLaunchScript;
/**
* Properties that should be expanded in the embedded launch script.
*/
Map<String,String> embeddedLaunchScriptProperties;
} }
...@@ -28,6 +28,8 @@ import org.gradle.api.Project; ...@@ -28,6 +28,8 @@ import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.bundling.Jar;
import org.springframework.boot.gradle.SpringBootPluginExtension; import org.springframework.boot.gradle.SpringBootPluginExtension;
import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.Repackager; import org.springframework.boot.loader.tools.Repackager;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
...@@ -80,6 +82,10 @@ public class RepackageTask extends DefaultTask { ...@@ -80,6 +82,10 @@ public class RepackageTask extends DefaultTask {
this.classifier = classifier; this.classifier = classifier;
} }
void setOutputFile(File file) {
this.outputFile = file;
}
@TaskAction @TaskAction
public void repackage() { public void repackage() {
Project project = getProject(); Project project = getProject();
...@@ -170,7 +176,8 @@ public class RepackageTask extends DefaultTask { ...@@ -170,7 +176,8 @@ public class RepackageTask extends DefaultTask {
} }
repackager.setBackupSource(this.extension.isBackupSource()); repackager.setBackupSource(this.extension.isBackupSource());
try { try {
repackager.repackage(file, this.libraries); LaunchScript launchScript = getLaunchScript();
repackager.repackage(file, this.libraries, launchScript);
} }
catch (IOException ex) { catch (IOException ex) {
throw new IllegalStateException(ex.getMessage(), ex); throw new IllegalStateException(ex.getMessage(), ex);
...@@ -201,6 +208,15 @@ public class RepackageTask extends DefaultTask { ...@@ -201,6 +208,15 @@ public class RepackageTask extends DefaultTask {
getLogger().info("Setting mainClass: " + mainClass); getLogger().info("Setting mainClass: " + mainClass);
repackager.setMainClass(mainClass); repackager.setMainClass(mainClass);
} }
private LaunchScript getLaunchScript() throws IOException {
if (this.extension.isExecutable()) {
return new DefaultLaunchScript(this.extension.getEmbeddedLaunchScript(),
this.extension.getEmbeddedLaunchScriptProperties());
}
return null;
}
} }
/** /**
...@@ -228,10 +244,7 @@ public class RepackageTask extends DefaultTask { ...@@ -228,10 +244,7 @@ public class RepackageTask extends DefaultTask {
} }
} }
} }
}
void setOutputFile(File file) {
this.outputFile = file;
} }
} }
/*
* Copyright 2012-2015 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.loader.tools;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Default implementation of {@link LaunchScript}. Provides the default Spring Boot launch
* script or can load a specific script File. Also support mustache style template
* expansion of the form <code>{{name:default}}</code>.
*
* @author Phillip Webb
* @since 1.3.0
*/
public class DefaultLaunchScript implements LaunchScript {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final int BUFFER_SIZE = 4096;
private static final Pattern PLACEHOLDER_PATTERN = Pattern
.compile("\\{\\{(\\w+)(:.*?)?\\}\\}");
private final String content;
/**
* Create a new {@link DefaultLaunchScript} instance.
* @param file the source script file or {@code null} to use the default
* @param properties an optional set of script properties used for variable expansion
* @throws IOException if the script cannot be loaded
*/
public DefaultLaunchScript(File file, Map<?, ?> properties) throws IOException {
String content = loadContent(file);
this.content = expandPlaceholders(content, properties);
}
private String loadContent(File file) throws IOException {
if (file == null) {
return loadContent(getClass().getResourceAsStream("launch.script"));
}
return loadContent(new FileInputStream(file));
}
private String loadContent(InputStream inputStream) throws IOException {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
copy(inputStream, outputStream);
return new String(outputStream.toByteArray(), UTF_8);
}
finally {
inputStream.close();
}
}
private void copy(InputStream inputStream, OutputStream outputStream)
throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
private String expandPlaceholders(String content, Map<?, ?> properties) {
StringBuffer expanded = new StringBuffer();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(content);
while (matcher.find()) {
String name = matcher.group(1);
String value = matcher.group(2);
if (properties != null && properties.containsKey(name)) {
value = (String) properties.get(name);
}
else {
value = (value == null ? matcher.group(0) : value.substring(1));
}
matcher.appendReplacement(expanded, value);
}
matcher.appendTail(expanded);
return expanded.toString();
}
@Override
public byte[] toByteArray() {
return this.content.getBytes(UTF_8);
}
}
...@@ -27,6 +27,9 @@ import java.io.IOException; ...@@ -27,6 +27,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
...@@ -63,7 +66,37 @@ public class JarWriter { ...@@ -63,7 +66,37 @@ public class JarWriter {
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public JarWriter(File file) throws FileNotFoundException, IOException { public JarWriter(File file) throws FileNotFoundException, IOException {
this.jarOutput = new JarOutputStream(new FileOutputStream(file)); this(file, null);
}
/**
* Create a new {@link JarWriter} instance.
* @param file the file to write
* @param launchScript an optional launch script to prepend to the front of the jar
* @throws IOException
* @throws FileNotFoundException
*/
public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundException,
IOException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
if (launchScript != null) {
fileOutputStream.write(launchScript.toByteArray());
setExecutableFilePermission(file);
}
this.jarOutput = new JarOutputStream(fileOutputStream);
}
private void setExecutableFilePermission(File file) {
try {
Path path = file.toPath();
Set<PosixFilePermission> permissions = new HashSet<PosixFilePermission>(
Files.getPosixFilePermissions(path));
permissions.add(PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(path, permissions);
}
catch (Throwable ex) {
// Ignore and continue creating the jar
}
} }
/** /**
......
/*
* Copyright 2012-2015 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.loader.tools;
/**
* A script that can be prepended to the front of a JAR file to make it executable.
*
* @author Phillip Webb
* @since 1.3.0
*/
public interface LaunchScript {
/**
* The the content of the launch script as a byte array.
* @return the script bytes
*/
byte[] toByteArray();
}
...@@ -104,17 +104,29 @@ public class Repackager { ...@@ -104,17 +104,29 @@ public class Repackager {
* @throws IOException * @throws IOException
*/ */
public void repackage(File destination, Libraries libraries) throws IOException { public void repackage(File destination, Libraries libraries) throws IOException {
repackage(destination, libraries, null);
}
/**
* Repackage to the given destination so that it can be launched using '
* {@literal java -jar}'
* @param destination the destination file (may be the same as the source)
* @param libraries the libraries required to run the archive
* @param launchScript an optional launch script prepended to the front of the jar
* @throws IOException
* @since 1.3.0
*/
public void repackage(File destination, Libraries libraries, LaunchScript launchScript)
throws IOException {
if (destination == null || destination.isDirectory()) { if (destination == null || destination.isDirectory()) {
throw new IllegalArgumentException("Invalid destination"); throw new IllegalArgumentException("Invalid destination");
} }
if (libraries == null) { if (libraries == null) {
throw new IllegalArgumentException("Libraries must not be null"); throw new IllegalArgumentException("Libraries must not be null");
} }
if (alreadyRepackaged()) { if (alreadyRepackaged()) {
return; return;
} }
destination = destination.getAbsoluteFile(); destination = destination.getAbsoluteFile();
File workingSource = this.source; File workingSource = this.source;
if (this.source.equals(destination)) { if (this.source.equals(destination)) {
...@@ -127,7 +139,7 @@ public class Repackager { ...@@ -127,7 +139,7 @@ public class Repackager {
try { try {
JarFile jarFileSource = new JarFile(workingSource); JarFile jarFileSource = new JarFile(workingSource);
try { try {
repackage(jarFileSource, destination, libraries); repackage(jarFileSource, destination, libraries, launchScript);
} }
finally { finally {
jarFileSource.close(); jarFileSource.close();
...@@ -152,9 +164,9 @@ public class Repackager { ...@@ -152,9 +164,9 @@ public class Repackager {
} }
} }
private void repackage(JarFile sourceJar, File destination, Libraries libraries) private void repackage(JarFile sourceJar, File destination, Libraries libraries,
throws IOException { LaunchScript launchScript) throws IOException {
final JarWriter writer = new JarWriter(destination); final JarWriter writer = new JarWriter(destination, launchScript);
try { try {
final Set<String> seen = new HashSet<String>(); final Set<String> seen = new HashSet<String>();
writer.writeManifest(buildManifest(sourceJar)); writer.writeManifest(buildManifest(sourceJar));
......
#!/bin/bash
#
# . ____ _ __ _ _
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
# ' |____| .__|_| |_|_| |_\__, | / / / /
# =========|_|==============|___/=/_/_/_/
# :: Spring Boot Startup Script ::
#
WORKING_DIR="$(pwd)"
PID_FOLDER="/var/run"
USER_PID_FOLDER="/tmp"
LOG_FOLDER="/var/log"
USER_LOG_FOLDER="/tmp"
# Setup defaults
[[ -z "$mode" ]] && mode="{{mode:auto}}" # modes are "auto", "service" or "run"
# ANSI Colors
echoRed() { echo $'\e[0;31m'$1$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'$1$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'$1$'\e[0m'; }
# Follow symlinks to find the real jar and detect init.d script
cd $(dirname "$0")
[[ -z "$jarfile" ]] && jarfile=$(pwd)/$(basename "$0")
while [[ -L "$jarfile" ]]; do
[[ "$jarfile" =~ "init.d" ]] && init_script=$(basename "$jarfile")
jarfile=$(readlink "$jarfile")
cd $(dirname "$jarfile")
jarfile=$(pwd)/$(basename "$jarfile")
done
cd "$WORKING_DIR"
# Determine the script mode
action="run"
if [[ "$mode" == "auto" && -n "$init_script" ]] || [[ "$mode" == "service" ]]; then
action="$1"
shift
fi
# Create an identity for log/pid files
if [[ -n "$init_script" ]]; then
identity="${init_script}"
else
jar_folder=$(dirname "$jarfile")
identity=$(basename "${jarfile%.*}")_${jar_folder//\//}
fi
# Build the pid and log filenames
if [[ -n "$init_script" ]]; then
pid_file="$PID_FOLDER/${identity}/${identity}.pid"
log_file="$LOG_FOLDER/${identity}.log"
else
pid_file="$USER_PID_FOLDER/${identity}.pid"
log_file="$USER_LOG_FOLDER/${identity}.log"
fi
# Determine the user to run as
[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$jarfile" | awk '{print $3}')
# Find Java
if type -p java 2>&1> /dev/null; then
javaexe=java
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
javaexe="$JAVA_HOME/bin/java"
elif [[ -x "/usr/bin/java" ]]; then
javaexe="/usr/bin/java"
else
echo "Unable to find Java"
exit 1
fi
# Build actual command to execute
command="$javaexe -jar -Dsun.misc.URLClassPath.disableJarChecking=true $jarfile $@"
# Utility functions
checkPermissions() {
touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; exit 1; }
touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; exit 1; }
}
isRunning() {
ps -p $1 &> /dev/null
}
# Action functions
start() {
if [[ -f "$pid_file" ]]; then
pid=$(cat "$pid_file")
isRunning $pid && { echoYellow "Already running [$pid]"; exit 0; }
fi
pushd $(dirname "$jarfile") > /dev/null
if [[ -n "$run_user" ]]; then
mkdir "$PID_FOLDER/${identity}" &> /dev/null
checkPermissions
chown "$run_user" "$PID_FOLDER/${identity}"
chown "$run_user" "$pid_file"
chown "$run_user" "$log_file"
su -c "$command &> \"$log_file\" & echo \$!" $run_user > "$pid_file"
pid=$(cat "$pid_file")
else
checkPermissions
$command &> "$log_file" &
pid=$!
disown $pid
echo "$pid" > "$pid_file"
fi
[[ -z $pid ]] && { echoRed "Failed to start"; exit 1; }
echoGreen "Started [$pid]"
}
stop() {
[[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; exit 1; }
pid=$(cat "$pid_file")
isRunning $pid || { echoRed "Not running (process ${pid} not found)"; exit 1; }
kill -HUP $pid &> /dev/null || { echoRed "Unable to kill process ${pid}"; exit 1; }
for i in $(seq 1 20); do
isRunning ${pid} || { echoGreen "Stopped [$pid]"; rm -f $pid_file; exit 0; }
sleep 1
done
echoRed "Unable to kill process ${pid}";
exit 3;
}
restart() {
stop
start
}
status() {
[[ -f $pid_file ]] || { echoRed "Not running"; exit 1; }
pid=$(cat "$pid_file")
isRunning $pid || { echoRed "Not running (process ${pid} not found)"; exit 1; }
echoGreen "Running [$pid]"
exit 0
}
run() {
pushd $(dirname "$jarfile") > /dev/null
exec $command
popd
}
# Call the appropriate action function
case "$action" in
start)
start "$@";;
stop)
stop "$@";;
restart)
restart "$@";;
status)
status "$@";;
run)
run "$@";;
*)
echo "Usage: $0 {start|stop|restart|status|run}"; exit 1;
esac
exit 0
/*
* Copyright 2012-2015 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.loader.tools;
import java.io.File;
import java.util.Properties;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileCopyUtils;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link DefaultLaunchScript}.
*
* @author Phillip Webb
*/
public class DefaultLaunchScriptTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void loadsDefaultScript() throws Exception {
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
String content = new String(script.toByteArray());
assertThat(content, containsString("Spring Boot Startup Script"));
assertThat(content, containsString("mode=\"auto\""));
}
@Test
public void loadFromFile() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("ABC".getBytes(), file);
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
String content = new String(script.toByteArray());
assertThat(content, equalTo("ABC"));
}
@Test
public void expandVariables() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
Properties properties = new Properties();
properties.put("a", "e");
properties.put("b", "o");
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
String content = new String(script.toByteArray());
assertThat(content, equalTo("hello"));
}
@Test
public void expandVariablesMultiLine() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("h{{a}}l\nl{{b}}".getBytes(), file);
Properties properties = new Properties();
properties.put("a", "e");
properties.put("b", "o");
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
String content = new String(script.toByteArray());
assertThat(content, equalTo("hel\nlo"));
}
@Test
public void expandVariablesWithDefaults() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
String content = new String(script.toByteArray());
assertThat(content, equalTo("hello"));
}
@Test
public void expandVariablesWithDefaultsOverride() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
Properties properties = new Properties();
properties.put("a", "a");
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
String content = new String(script.toByteArray());
assertThat(content, equalTo("hallo"));
}
@Test
public void expandVariablesMissingAreUnchanged() throws Exception {
File file = this.temporaryFolder.newFile();
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
String content = new String(script.toByteArray());
assertThat(content, equalTo("h{{a}}ll{{b}}"));
}
}
...@@ -18,6 +18,8 @@ package org.springframework.boot.loader.tools; ...@@ -18,6 +18,8 @@ package org.springframework.boot.loader.tools;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.jar.Attributes; import java.util.jar.Attributes;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
...@@ -34,6 +36,7 @@ import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod; ...@@ -34,6 +36,7 @@ import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
...@@ -141,7 +144,6 @@ public class RepackagerTests { ...@@ -141,7 +144,6 @@ public class RepackagerTests {
Repackager repackager = new Repackager(file); Repackager repackager = new Repackager(file);
repackager.repackage(NO_LIBRARIES); repackager.repackage(NO_LIBRARIES);
repackager.repackage(NO_LIBRARIES); repackager.repackage(NO_LIBRARIES);
Manifest actualManifest = getManifest(file); Manifest actualManifest = getManifest(file);
assertThat(actualManifest.getMainAttributes().getValue("Main-Class"), assertThat(actualManifest.getMainAttributes().getValue("Main-Class"),
equalTo("org.springframework.boot.loader.JarLauncher")); equalTo("org.springframework.boot.loader.JarLauncher"));
...@@ -230,7 +232,6 @@ public class RepackagerTests { ...@@ -230,7 +232,6 @@ public class RepackagerTests {
equalTo(false)); equalTo(false));
assertThat(hasLauncherClasses(source), equalTo(false)); assertThat(hasLauncherClasses(source), equalTo(false));
assertThat(hasLauncherClasses(dest), equalTo(true)); assertThat(hasLauncherClasses(dest), equalTo(true));
} }
@Test @Test
...@@ -380,7 +381,6 @@ public class RepackagerTests { ...@@ -380,7 +381,6 @@ public class RepackagerTests {
callback.library(new Library(nestedFile, LibraryScope.COMPILE)); callback.library(new Library(nestedFile, LibraryScope.COMPILE));
} }
}); });
JarFile jarFile = new JarFile(file); JarFile jarFile = new JarFile(file);
try { try {
assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getMethod(), assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getMethod(),
...@@ -393,6 +393,22 @@ public class RepackagerTests { ...@@ -393,6 +393,22 @@ public class RepackagerTests {
} }
} }
@Test
public void addLauncherScript() throws Exception {
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
File source = this.testJarFile.getFile();
File dest = this.temporaryFolder.newFile("dest.jar");
Repackager repackager = new Repackager(source);
LaunchScript script = new MockLauncherScript("ABC");
repackager.repackage(dest, NO_LIBRARIES, script);
byte[] bytes = FileCopyUtils.copyToByteArray(dest);
assertThat(Files.getPosixFilePermissions(dest.toPath()),
hasItem(PosixFilePermission.OWNER_EXECUTE));
assertThat(new String(bytes), startsWith("ABC"));
assertThat(hasLauncherClasses(source), equalTo(false));
assertThat(hasLauncherClasses(dest), equalTo(true));
}
private boolean hasLauncherClasses(File file) throws IOException { private boolean hasLauncherClasses(File file) throws IOException {
return hasEntry(file, "org/springframework/boot/") return hasEntry(file, "org/springframework/boot/")
&& hasEntry(file, "org/springframework/boot/loader/JarLauncher.class"); && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");
...@@ -422,4 +438,19 @@ public class RepackagerTests { ...@@ -422,4 +438,19 @@ public class RepackagerTests {
} }
} }
private static class MockLauncherScript implements LaunchScript {
private final byte[] bytes;
public MockLauncherScript(String script) {
this.bytes = script.getBytes();
}
@Override
public byte[] toByteArray() {
return this.bytes;
}
}
} }
<?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.maven.it</groupId>
<artifactId>jar</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<embeddedLaunchScript>${basedir}/src/launcher/custom.script</embeddedLaunchScript>
<embeddedLaunchScriptProperties>
<name>world</name>
</embeddedLaunchScriptProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<mainClass>some.random.Main</mainClass>
</manifest>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring.version@</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>@servlet-api.version@</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
import java.io.*;
import org.springframework.boot.maven.*;
Verify.verifyJar(
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", "Hello world"
);
<?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.maven.it</groupId>
<artifactId>jar</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<executable>false</executable>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<mainClass>some.random.Main</mainClass>
</manifest>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring.version@</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>@servlet-api.version@</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
import java.io.*;
import org.springframework.boot.maven.*;
Verify.verifyJar(
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", false
);
...@@ -2,6 +2,6 @@ import java.io.*; ...@@ -2,6 +2,6 @@ import java.io.*;
import org.springframework.boot.maven.*; import org.springframework.boot.maven.*;
Verify.verifyJar( Verify.verifyJar(
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main" new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", "Spring Boot Startup Script"
); );
...@@ -19,6 +19,7 @@ package org.springframework.boot.maven; ...@@ -19,6 +19,7 @@ package org.springframework.boot.maven;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile; import java.util.jar.JarFile;
...@@ -34,6 +35,8 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -34,6 +35,8 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.project.MavenProjectHelper;
import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
...@@ -124,6 +127,29 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { ...@@ -124,6 +127,29 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
@Parameter @Parameter
private List<Dependency> requiresUnpack; private List<Dependency> requiresUnpack;
/**
* Make a fully executable jar for *nix machines by prepending a launch script to the
* jar.
* @since 1.3
*/
@Parameter(defaultValue = "true")
private boolean executable;
/**
* The embedded launch script to prepend to the front of the jar if it is fully
* executable. If not specified the 'Spring Boot' default script will be used.
* @since 1.3
*/
@Parameter
private File embeddedLaunchScript;
/**
* Properties that should be expanded in the embedded launch script.
* @since 1.3
*/
@Parameter
private Properties embeddedLaunchScriptProperties;
@Override @Override
public void execute() throws MojoExecutionException, MojoFailureException { public void execute() throws MojoExecutionException, MojoFailureException {
if (this.project.getPackaging().equals("pom")) { if (this.project.getPackaging().equals("pom")) {
...@@ -167,7 +193,8 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { ...@@ -167,7 +193,8 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack, Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
getLog()); getLog());
try { try {
repackager.repackage(target, libraries); LaunchScript launchScript = getLaunchScript();
repackager.repackage(target, libraries, launchScript);
} }
catch (IOException ex) { catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex); throw new MojoExecutionException(ex.getMessage(), ex);
...@@ -190,6 +217,14 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { ...@@ -190,6 +217,14 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
+ this.project.getPackaging()); + this.project.getPackaging());
} }
private LaunchScript getLaunchScript() throws IOException {
if (this.executable) {
return new DefaultLaunchScript(this.embeddedLaunchScript,
this.embeddedLaunchScriptProperties);
}
return null;
}
public static enum LayoutType { public static enum LayoutType {
/** /**
......
...@@ -26,8 +26,12 @@ import java.util.jar.Manifest; ...@@ -26,8 +26,12 @@ import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.springframework.util.FileCopyUtils;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
...@@ -44,8 +48,14 @@ public class Verify { ...@@ -44,8 +48,14 @@ public class Verify {
new JarArchiveVerification(file, SAMPLE_APP).verify(); new JarArchiveVerification(file, SAMPLE_APP).verify();
} }
public static void verifyJar(File file, String main) throws Exception { public static void verifyJar(File file, String main, String... scriptContents)
new JarArchiveVerification(file, main).verify(); throws Exception {
verifyJar(file, main, true, scriptContents);
}
public static void verifyJar(File file, String main, boolean executable,
String... scriptContents) throws Exception {
new JarArchiveVerification(file, main).verify(executable, scriptContents);
} }
public static void verifyWar(File file) throws Exception { public static void verifyWar(File file) throws Exception {
...@@ -149,9 +159,30 @@ public class Verify { ...@@ -149,9 +159,30 @@ public class Verify {
} }
public void verify() throws Exception { public void verify() throws Exception {
verify(true);
}
public void verify(boolean executable, String... scriptContents) throws Exception {
assertTrue("Archive missing", this.file.exists()); assertTrue("Archive missing", this.file.exists());
assertTrue("Archive not a file", this.file.isFile()); assertTrue("Archive not a file", this.file.isFile());
if (scriptContents.length > 0 && executable) {
String contents = new String(FileCopyUtils.copyToByteArray(this.file));
contents = contents.substring(0, contents.indexOf(new String(new byte[] {
0x50, 0x4b, 0x03, 0x04 })));
for (String content : scriptContents) {
assertThat(contents, containsString(content));
}
}
if (!executable) {
String contents = new String(FileCopyUtils.copyToByteArray(this.file));
assertTrue(
"Is executable",
contents.startsWith(new String(new byte[] { 0x50, 0x4b, 0x03,
0x04 })));
}
ZipFile zipFile = new ZipFile(this.file); ZipFile zipFile = new ZipFile(this.file);
try { try {
ArchiveVerifier verifier = new ArchiveVerifier(zipFile); ArchiveVerifier verifier = new ArchiveVerifier(zipFile);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment