Merge branch 'gh-1117'
This commit is contained in:
commit
ba2ea301f1
|
|
@ -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`.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
[[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.
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
[[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
|
||||||
|
|
||||||
|
|
||||||
[[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.
|
||||||
|
|
||||||
|
|
||||||
[[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.
|
||||||
|
|
||||||
|
|
||||||
[[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.
|
||||||
|
|
||||||
[[cloud-deployment-whats-next]]
|
|
||||||
|
|
||||||
|
=== 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.
|
||||||
|
|
||||||
|
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
|
||||||
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
|
||||||
== 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[]
|
||||||
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
|
||||||
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 @@
|
||||||
[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 @@
|
||||||
|
|
||||||
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 {
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void setOutputFile(File file) {
|
|
||||||
this.outputFile = file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
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 {
|
||||||
* @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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
* @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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
#!/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
|
||||||
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?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>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Hello {{name}}"
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?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>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
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.*;
|
||||||
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;
|
||||||
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;
|
||||||
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 {
|
||||||
@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 {
|
||||||
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 {
|
||||||
+ 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;
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue