diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java index 03b2a90cf4..36f4ba5dfd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Map; /** * Utility used to run a process. @@ -28,6 +30,7 @@ import java.util.Collection; * @author Dave Syer * @author Andy Wilkinson * @author Stephane Nicoll + * @author Dmytro Nosan * @since 1.1.0 */ public class RunProcess { @@ -63,14 +66,15 @@ public class RunProcess { } public int run(boolean waitForProcess, String... args) throws IOException { - return run(waitForProcess, Arrays.asList(args)); + return run(waitForProcess, Arrays.asList(args), Collections.emptyMap()); } - protected int run(boolean waitForProcess, Collection args) - throws IOException { + public int run(boolean waitForProcess, Collection args, + Map environmentVariables) throws IOException { ProcessBuilder builder = new ProcessBuilder(this.command); builder.directory(this.workingDirectory); builder.command().addAll(args); + builder.environment().putAll(environmentVariables); builder.redirectErrorStream(true); builder.inheritIO(); try { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/pom.xml new file mode 100644 index 0000000000..b14692f606 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + org.springframework.boot.maven.it + run-envargs + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + package + + run + + + + 5000 + Some Text + + + + + + + + + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/src/main/java/org/test/SampleApplication.java new file mode 100644 index 0000000000..c60eabec39 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2018 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) { + assertEnvValue("ENV1", "5000"); + assertEnvValue("ENV2", "Some Text"); + assertEnvValue("ENV3", ""); + assertEnvValue("ENV4", ""); + + System.out.println("I haz been run"); + } + + private static void assertEnvValue(String envKey, String expectedValue) { + String actual = System.getenv(envKey); + if (!expectedValue.equals(actual)) { + throw new IllegalStateException("env property [" + envKey + "] mismatch " + + "(got [" + actual + "], expected [" + expectedValue + "]"); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/verify.groovy b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/verify.groovy new file mode 100644 index 0000000000..841c4a97de --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/it/run-envargs/verify.groovy @@ -0,0 +1,3 @@ +def file = new File(basedir, "build.log") +return file.text.contains("I haz been run") + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java index 2eb74cad2b..74767347c5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractRunMojo.java @@ -48,6 +48,7 @@ import org.springframework.boot.loader.tools.MainClassFinder; * @author Stephane Nicoll * @author David Liu * @author Daniel Young + * @author Dmytro Nosan * @see RunMojo * @see StartMojo */ @@ -115,6 +116,15 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { @Parameter private Map systemPropertyVariables; + /** + * List of Environment variables that should be associated with the forked process + * used to run the application. NOTE: the use of Environment variables means that + * processes will be started by forking a new JVM. + * @since 2.1 + */ + @Parameter + private Map environmentVariables; + /** * Arguments that should be passed to the application. On command line use commas to * separate multiple arguments. @@ -203,7 +213,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { * @see #logDisabledFork() */ protected boolean enableForkByDefault() { - return hasAgent() || hasJvmArgs() || hasWorkingDirectorySet(); + return hasAgent() || hasJvmArgs() || hasEnvVariables() || hasWorkingDirectorySet(); } private boolean hasAgent() { @@ -216,6 +226,10 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { && !this.systemPropertyVariables.isEmpty()); } + private boolean hasEnvVariables() { + return (this.environmentVariables != null && !this.environmentVariables.isEmpty()); + } + private boolean hasWorkingDirectorySet() { return this.workingDirectory != null; } @@ -262,17 +276,19 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { addClasspath(args); args.add(startClassName); addArgs(args); - runWithForkedJvm(this.workingDirectory, args); + runWithForkedJvm(this.workingDirectory, args, determineEnvironmentVariables()); } /** * Run with a forked VM, using the specified command line arguments. * @param workingDirectory the working directory of the forked JVM * @param args the arguments (JVM arguments and application arguments) + * @param environmentVariables the environment variables * @throws MojoExecutionException in case of MOJO execution errors * @throws MojoFailureException in case of MOJO failures */ - protected abstract void runWithForkedJvm(File workingDirectory, List args) + protected abstract void runWithForkedJvm(File workingDirectory, List args, + Map environmentVariables) throws MojoExecutionException, MojoFailureException; /** @@ -295,12 +311,26 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { return runArguments; } + /** + * Resolve the environment variables to use. + * @return a {@link EnvVariables} defining the environment variables + */ + protected EnvVariables resolveEnvVariables() { + return new EnvVariables(this.environmentVariables); + } + private void addArgs(List args) { RunArguments applicationArguments = resolveApplicationArguments(); Collections.addAll(args, applicationArguments.asArray()); logArguments("Application argument(s): ", this.arguments); } + private Map determineEnvironmentVariables() { + EnvVariables envVariables = resolveEnvVariables(); + logArguments("Environment variable(s): ", envVariables.asArray()); + return envVariables.asMap(); + } + /** * Resolve the JVM arguments to use. * @return a {@link RunArguments} defining the JVM arguments diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/EnvVariables.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/EnvVariables.java new file mode 100644 index 0000000000..a38d4876e1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/EnvVariables.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2018 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.maven; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class for working with Env variables. + * + * @author Dmytro Nosan + */ +class EnvVariables { + + private final Map variables; + + EnvVariables(Map variables) { + this.variables = parseEnvVariables(variables); + } + + private static Map parseEnvVariables(Map args) { + if (args == null || args.isEmpty()) { + return Collections.emptyMap(); + } + Map result = new LinkedHashMap<>(); + for (Map.Entry e : args.entrySet()) { + if (e.getKey() != null) { + result.put(e.getKey(), getValue(e.getValue())); + } + } + return result; + } + + private static String getValue(String value) { + return (value != null ? value : ""); + } + + public Map asMap() { + return Collections.unmodifiableMap(this.variables); + } + + public String[] asArray() { + List args = new ArrayList<>(this.variables.size()); + for (Map.Entry arg : this.variables.entrySet()) { + args.add(arg.getKey() + "=" + arg.getValue()); + } + return args.toArray(new String[0]); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java index 1280b88162..cf75bd6b50 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RunMojo.java @@ -20,6 +20,7 @@ import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.List; +import java.util.Map; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Execute; @@ -34,6 +35,7 @@ import org.springframework.boot.loader.tools.RunProcess; * Run an executable archive application. * * @author Phillip Webb + * @author Dmytro Nosan * @author Stephane Nicoll * @author Andy Wilkinson */ @@ -64,14 +66,15 @@ public class RunMojo extends AbstractRunMojo { } @Override - protected void runWithForkedJvm(File workingDirectory, List args) + protected void runWithForkedJvm(File workingDirectory, List args, + Map environmentVariables) throws MojoExecutionException { try { RunProcess runProcess = new RunProcess(workingDirectory, new JavaExecutable().toString()); Runtime.getRuntime() .addShutdownHook(new Thread(new RunProcessKiller(runProcess))); - int exitCode = runProcess.run(true, args.toArray(new String[0])); + int exitCode = runProcess.run(true, args, environmentVariables); if (exitCode == 0 || exitCode == EXIT_CODE_SIGINT) { return; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java index 9a27ca8479..151981c6c2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/StartMojo.java @@ -23,6 +23,7 @@ import java.net.ConnectException; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import javax.management.MBeanServerConnection; @@ -88,9 +89,10 @@ public class StartMojo extends AbstractRunMojo { private final Object lock = new Object(); @Override - protected void runWithForkedJvm(File workingDirectory, List args) + protected void runWithForkedJvm(File workingDirectory, List args, + Map environmentVariables) throws MojoExecutionException, MojoFailureException { - RunProcess runProcess = runProcess(workingDirectory, args); + RunProcess runProcess = runProcess(workingDirectory, args, environmentVariables); try { waitForSpringApplication(); } @@ -100,12 +102,13 @@ public class StartMojo extends AbstractRunMojo { } } - private RunProcess runProcess(File workingDirectory, List args) + private RunProcess runProcess(File workingDirectory, List args, + Map environmentVariables) throws MojoExecutionException { try { RunProcess runProcess = new RunProcess(workingDirectory, new JavaExecutable().toString()); - runProcess.run(false, args.toArray(new String[0])); + runProcess.run(false, args, environmentVariables); return runProcess; } catch (Exception ex) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/run-env-variables.apt.vm b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/run-env-variables.apt.vm new file mode 100644 index 0000000000..855fc8a329 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/examples/run-env-variables.apt.vm @@ -0,0 +1,50 @@ + ----- + Using environment variables + ----- +Dmytro Nosan + ----- + 2018-04-08 + ----- + + Environment variables can be specified using the <<>> attribute. + The following sets the 'ENV1', 'ENV2', 'ENV3', 'ENV4' env variables: + +--- + + ... + + ... + + ... + + ${project.groupId} + ${project.artifactId} + ${project.version} + + + 5000 + Some Text + + + + + ... + + ... + + ... + + ... + +--- + + If the value is empty or not defined (i.e. <<<>>>), the env variable is set + with an empty String as the value. + + Any String typed Maven variable can be passed as system properties. Any attempt to pass + any other Maven variable type (e.g. a <<>> or a <<>> variable) will cause the + variable expression to be passed literally (unevaluated). + + Environment variables defined this way take precedence over existing values. + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt index f3e47a5d96..22fa6c63ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/index.apt @@ -54,6 +54,8 @@ Spring Boot Maven Plugin * {{{./examples/run-system-properties.html}Using system properties}} + * {{{./examples/run-env-variables.html}Using environment variables}} + * {{{./examples/it-random-port.html}Random port for integration tests}} * {{{./examples/it-skip.html}Skip integration tests}} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/usage.apt.vm b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/usage.apt.vm index ffa09b3e1f..f7cc89ed06 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/usage.apt.vm +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/apt/usage.apt.vm @@ -135,15 +135,17 @@ mvn spring-boot:run By default the application is executed directly from the Maven JVM. If you need to run in a forked process you can use the 'fork' option. Forking will also occur if the - 'jvmArguments', 'systemPropertyVariables' or 'agent' options are specified, or if - devtools is present. + 'jvmArguments', 'systemPropertyVariables', 'environmentVariables' or 'agent' options + are specified, or if devtools is present. If you need to specify some JVM arguments (i.e. for debugging purposes), you can use the <<>> parameter, see {{{./examples/run-debug.html}Debug the application}} - for more details. There is also explicit support - {{{./examples/run-system-properties.html}for system properties}}. As a convenience, the - profiles to enable are handled by a specific property (<<>>), see - {{{./examples/run-profiles.html}Specify active profiles}}. + for more details. There is also explicit support for + {{{./examples/run-system-properties.html}system properties}} and + {{{./examples/run-env-variables.html}environment variables}}. + + As a convenience, the profiles to enable are handled by a specific property ( + <<>>), see {{{./examples/run-profiles.html}Specify active profiles}}. Spring Boot 1.3 has introduced <<>>, a module to improve the development-time experience when working on Spring Boot applications. To enable it, just add the following diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/site.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/site.xml index 5e1157ab54..89218fd978 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/site.xml +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/site/site.xml @@ -12,6 +12,7 @@ + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/EnvVariablesTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/EnvVariablesTests.java new file mode 100644 index 0000000000..21242e19b7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/EnvVariablesTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2018 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.maven; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Tests for {@link EnvVariables}. + * + * @author Dmytro Nosan + */ +public class EnvVariablesTests { + + @Test + public void asNull() { + Map args = new EnvVariables(null).asMap(); + assertThat(args).isEmpty(); + } + + @Test + public void asArray() { + assertThat(new EnvVariables(getTestArgs()).asArray()) + .contains("key=My Value", "key1= tt ", "key2= ", "key3="); + } + + @Test + public void asMap() { + assertThat(new EnvVariables(getTestArgs()).asMap()).containsExactly( + entry("key", "My Value"), entry("key1", " tt "), entry("key2", " "), + entry("key3", "")); + } + + private Map getTestArgs() { + Map args = new LinkedHashMap<>(); + args.put("key", "My Value"); + args.put("key1", " tt "); + args.put("key2", " "); + args.put("key3", null); + return args; + } + +}