├── .travis.yml ├── README.md ├── .gitignore ├── src ├── test │ ├── resources │ │ └── logback-test.xml │ └── java │ │ └── com │ │ └── spotify │ │ └── spawn │ │ └── SubprocessesTest.java └── main │ └── java │ └── com │ └── spotify │ └── spawn │ ├── Trampoline.java │ ├── Subprocess.java │ └── Subprocesses.java ├── pom.xml └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | install: true 7 | 8 | script: mvn clean test 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | spawn 2 | ===== 3 | 4 | [![Build Status](https://travis-ci.org/spotify/spawn.svg?branch=master)](https://travis-ci.org/spotify/spawn) 5 | 6 | A Java library for managing child processes. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | target/ 15 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | System.err 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/spawn/Trampoline.java: -------------------------------------------------------------------------------- 1 | package com.spotify.spawn; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Arrays; 5 | 6 | import jnr.posix.POSIXFactory; 7 | 8 | /** 9 | * Subprocess trampoline. Takes the parent pid and the main class as the first two arguments. The rest of the 10 | * arguments are passed on to the subprocess main class. 11 | */ 12 | class Trampoline { 13 | 14 | private static final String[] EMPTY_ARGS = {}; 15 | 16 | public static void main(String[] args) throws Exception { 17 | System.out.flush(); 18 | if (args.length < 2) { 19 | System.err.println("invalid arguments: " + Arrays.toString(args)); 20 | System.exit(Subprocesses.INVALID_ARGUMENTS_EXIT_CODE); 21 | return; 22 | } 23 | try { 24 | final int ppid = Integer.valueOf(args[0]); 25 | monitorParent(ppid); 26 | final String main = args[1]; 27 | final String[] mainArgs = args.length > 2 ? Arrays.copyOfRange(args, 2, args.length) : EMPTY_ARGS; 28 | final Class mainClass = Class.forName(main); 29 | final Method mainMethod = Subprocesses.findMain(mainClass); 30 | if (mainMethod == null) { 31 | System.err.println("Main method not found: " + main); 32 | System.exit(Subprocesses.INVALID_ARGUMENTS_EXIT_CODE); 33 | } 34 | mainMethod.invoke(null, (Object) mainArgs); 35 | } catch (Throwable e) { 36 | e.printStackTrace(); 37 | System.exit(Subprocesses.EXCEPTION_EXIT_CODE); 38 | } 39 | } 40 | 41 | private static void monitorParent(final int pid) { 42 | Subprocesses.startDaemon(() -> { 43 | while (true) { 44 | try { 45 | Thread.sleep(200); 46 | } catch (InterruptedException e) { 47 | continue; 48 | } 49 | int ppid = POSIXFactory.getPOSIX().getppid(); 50 | if (ppid != pid) { 51 | // If we've been reparented, then the spawning parent is dead. 52 | System.exit(Subprocesses.OK_EXIT_CODE); 53 | } 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/spawn/Subprocess.java: -------------------------------------------------------------------------------- 1 | package com.spotify.spawn; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import static com.spotify.spawn.Subprocesses.startDaemon; 11 | 12 | /** 13 | * A process that terminates if the parent exits. 14 | */ 15 | public class Subprocess { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(Subprocess.class); 18 | 19 | private final AtomicBoolean killed = new AtomicBoolean(); 20 | 21 | private final Process process; 22 | private final String main; 23 | private final Integer parentExitCode; 24 | 25 | Subprocess(final Process process, final String main, final Integer parentExitCode) { 26 | this.process = process; 27 | this.main = main; 28 | this.parentExitCode = parentExitCode; 29 | monitor(); 30 | } 31 | 32 | // TODO (dano): communicate the child process pid to the parent via the trampoline using a pipe 33 | // public int pid() { 34 | // 35 | // } 36 | 37 | /** 38 | * Kill the process. 39 | */ 40 | public void kill() { 41 | killed.set(true); 42 | // TODO (dano): send SIGKILL after a timeout if process doesn't exit on SIGTERM. 43 | process.destroy(); 44 | } 45 | 46 | /** 47 | * Check if the process was killed by a call to {@link #kill()}. 48 | */ 49 | public boolean killed() { 50 | return killed.get(); 51 | } 52 | 53 | /** 54 | * Block until the process exits. 55 | */ 56 | public int join() throws InterruptedException { 57 | return process.waitFor(); 58 | } 59 | 60 | /** 61 | * Check if the process is still running. 62 | */ 63 | public boolean running() { 64 | try { 65 | process.exitValue(); 66 | return false; 67 | } catch (IllegalThreadStateException e) { 68 | return true; 69 | } 70 | } 71 | 72 | /** 73 | * Get stdin of the process. 74 | */ 75 | public OutputStream stdin() { 76 | return process.getOutputStream(); 77 | } 78 | 79 | /** 80 | * Get stdout of the process. 81 | */ 82 | public InputStream stdout() { 83 | return process.getInputStream(); 84 | } 85 | 86 | /** 87 | * Get stderr of the process. 88 | */ 89 | public InputStream stderr() { 90 | return process.getErrorStream(); 91 | } 92 | 93 | /** 94 | * Access the underlying {@link Process}. 95 | */ 96 | public Process process() { 97 | return process; 98 | } 99 | 100 | /** 101 | * Monitor the subprocess, log when it exits and optionally terminate the parent as well. 102 | */ 103 | private void monitor() { 104 | startDaemon(() -> { 105 | while (true) { 106 | try { 107 | final int exitCode = join(); 108 | if (killed()) { 109 | return; 110 | } 111 | if (parentExitCode != null) { 112 | log.error("{} exited: {}. Exiting.", exitCode); 113 | System.exit(parentExitCode); 114 | } else if (exitCode != 0) { 115 | log.warn("{} exited: {}", main, exitCode); 116 | } else { 117 | log.info("{} exited: 0"); 118 | } 119 | return; 120 | } catch (InterruptedException ignored) { 121 | } 122 | } 123 | }); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/com/spotify/spawn/SubprocessesTest.java: -------------------------------------------------------------------------------- 1 | package com.spotify.spawn; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.nio.channels.FileChannel; 8 | import java.nio.channels.FileLock; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | 13 | import static java.lang.ProcessBuilder.Redirect.INHERIT; 14 | import static java.nio.file.StandardOpenOption.WRITE; 15 | import static org.hamcrest.Matchers.is; 16 | import static org.hamcrest.Matchers.nullValue; 17 | import static org.junit.Assert.assertThat; 18 | 19 | public class SubprocessesTest { 20 | 21 | public static class Foo { 22 | 23 | public static void main(final String... args) throws IOException { 24 | System.out.write("Foo!".getBytes("UTF-8")); 25 | System.out.flush(); 26 | } 27 | } 28 | 29 | @Test 30 | public void testSpawn() throws Exception { 31 | final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); 32 | 33 | final Subprocess subprocess = Subprocesses.process() 34 | .main(Foo.class) 35 | .pipeStdout(stdout) 36 | .spawn(); 37 | 38 | subprocess.join(); 39 | 40 | final String output = stdout.toString("UTF-8"); 41 | 42 | assertThat(output, is("Foo!")); 43 | } 44 | 45 | public static class Parent { 46 | 47 | public static class Child { 48 | 49 | public static void main(final String... args) throws IOException { 50 | assertThat(args.length, is(1)); 51 | final String lockfilename = args[0]; 52 | final FileChannel lockfile = FileChannel.open(Paths.get(lockfilename), WRITE); 53 | System.err.println("locking " + lockfilename); 54 | System.err.flush(); 55 | lockfile.lock(); 56 | System.err.println("locked " + lockfilename); 57 | System.err.flush(); 58 | System.out.write(("locked " + lockfilename).getBytes("UTF-8")); 59 | System.out.flush(); 60 | while (true) { 61 | try { 62 | Thread.sleep(1000); 63 | } catch (InterruptedException ignore) { 64 | } 65 | } 66 | } 67 | } 68 | 69 | public static void main(final String... args) throws IOException { 70 | final Subprocess child = Subprocesses.process() 71 | .main(Child.class) 72 | .inheritIO() 73 | .args(args) 74 | .spawn(); 75 | while (true) { 76 | try { 77 | Thread.sleep(1000); 78 | } catch (InterruptedException ignore) { 79 | } 80 | } 81 | } 82 | } 83 | 84 | @Test 85 | public void verifySubprocessTerminatesWhenParentExits() throws Exception { 86 | final Path lockFilePath = Files.createTempFile("subprocesses", ".lock"); 87 | final FileChannel lockFile = FileChannel.open(lockFilePath, WRITE); 88 | 89 | final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); 90 | 91 | final String lockFilename = lockFilePath.toAbsolutePath().toString(); 92 | final Subprocess parent = Subprocesses.process() 93 | .main(Parent.class) 94 | .args(lockFilename) 95 | .redirectStderr(INHERIT) 96 | .pipeStdout(stdout) 97 | .spawn(); 98 | 99 | // Wait for child to lock file 100 | while (true) { 101 | if (stdout.size() == 0) { 102 | Thread.sleep(100); 103 | continue; 104 | } 105 | final String out = stdout.toString("UTF-8"); 106 | assertThat(out, is("locked " + lockFilename)); 107 | break; 108 | } 109 | 110 | // Verify that we cannot take the lock while the child is alive 111 | assertThat(lockFile.tryLock(), is(nullValue())); 112 | 113 | // Kill the parent 114 | parent.kill(); 115 | 116 | // Verify that the child exits by taking the file lock 117 | while (true) { 118 | final FileLock lock = lockFile.tryLock(); 119 | if (lock != null) { 120 | break; 121 | } 122 | Thread.sleep(1000); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.spotify 5 | spawn 6 | 0.3-SNAPSHOT 7 | jar 8 | 9 | spawn 10 | https://github.com/spotify/spawn 11 | 12 | 13 | org.sonatype.oss 14 | oss-parent 15 | 9 16 | 17 | 18 | 19 | 20 | Apache License, Version 2.0 21 | http://www.apache.org/licenses/LICENSE-2.0.txt 22 | repo 23 | 24 | 25 | 26 | 27 | scm:git:https://github.com/spotify/spawn.git 28 | scm:git:git@github.com:spotify/spawn.git 29 | https://github.com/spotify/spawn 30 | HEAD 31 | 32 | 33 | 34 | 35 | danielnorberg 36 | dano@spotify.com 37 | Daniel Norberg 38 | 39 | 40 | 41 | 42 | UTF-8 43 | 44 | 45 | 46 | 47 | com.github.jnr 48 | jnr-posix 49 | 3.0.23 50 | 51 | 52 | org.slf4j 53 | slf4j-api 54 | 1.7.12 55 | 56 | 57 | 58 | 59 | junit 60 | junit 61 | 4.12 62 | test 63 | 64 | 65 | ch.qos.logback 66 | logback-classic 67 | 1.1.2 68 | test 69 | 70 | 71 | org.hamcrest 72 | hamcrest-library 73 | 1.3 74 | test 75 | 76 | 77 | org.mockito 78 | mockito-core 79 | 1.10.19 80 | test 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-release-plugin 89 | 2.5 90 | 91 | v@{project.version} 92 | 93 | 94 | 95 | org.apache.maven.scm 96 | maven-scm-provider-gitexe 97 | 1.9 98 | 99 | 100 | 101 | 102 | org.sonatype.plugins 103 | nexus-staging-maven-plugin 104 | 1.6.5 105 | true 106 | 107 | ossrh 108 | https://oss.sonatype.org/ 109 | true 110 | 111 | 112 | 113 | maven-compiler-plugin 114 | 3.3 115 | 116 | 1.8 117 | 1.8 118 | false 119 | 120 | -Xlint 121 | 122 | true 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-enforcer-plugin 128 | 1.4 129 | 130 | 131 | enforce 132 | 133 | 134 | 135 | 136 | 137 | 138 | enforce 139 | 140 | 141 | 142 | 143 | 144 | maven-failsafe-plugin 145 | 2.18.1 146 | 147 | 148 | 149 | integration-test 150 | verify 151 | 152 | 153 | 154 | 155 | 156 | org.jacoco 157 | jacoco-maven-plugin 158 | 0.7.5.201505241946 159 | 160 | 161 | 162 | prepare-agent 163 | 164 | 165 | 166 | report 167 | test 168 | 169 | report 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/com/spotify/spawn/Subprocesses.java: -------------------------------------------------------------------------------- 1 | package com.spotify.spawn; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Modifier; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import jnr.posix.POSIXFactory; 19 | 20 | import static java.lang.ProcessBuilder.Redirect.PIPE; 21 | import static java.util.Arrays.asList; 22 | 23 | /** 24 | * Utility for spawning subprocesses that terminate when the parent (the spawning process) exits. 25 | */ 26 | public class Subprocesses { 27 | 28 | private static final Logger log = LoggerFactory.getLogger(Subprocesses.class); 29 | 30 | public static final int OK_EXIT_CODE = 0; 31 | public static final int INVALID_ARGUMENTS_EXIT_CODE = 2; 32 | public static final int EXCEPTION_EXIT_CODE = 3; 33 | public static final int SUBPROCESS_EXIT_CODE = 4; 34 | 35 | /** 36 | * Create a builder for a new {@link Subprocess}. 37 | */ 38 | public static SubprocessBuilder process() { 39 | return new SubprocessBuilder(); 40 | } 41 | 42 | public static class SubprocessBuilder { 43 | 44 | private final ProcessBuilder processBuilder = new ProcessBuilder(); 45 | private OutputStream stdoutPipe; 46 | private OutputStream stderrPipe; 47 | private InputStream stdinPipe; 48 | 49 | public SubprocessBuilder() { 50 | } 51 | 52 | private Integer parentExitCodeOnSubprocessExit; 53 | private List jvmArgs = Collections.emptyList(); 54 | private String main; 55 | private List args = Collections.emptyList(); 56 | 57 | /** 58 | * Set an exit code that the parent (this) process should exit with if the subprocess exits. {@code null} to not 59 | * terminate the parent on subprocess exit. 60 | */ 61 | public SubprocessBuilder terminateParentOnSubprocessExit(final Integer exitCode) { 62 | this.parentExitCodeOnSubprocessExit = exitCode; 63 | return this; 64 | } 65 | 66 | /** 67 | * Terminate the parent on subprocess exit with exit code {@link Subprocesses#SUBPROCESS_EXIT_CODE} 68 | */ 69 | public SubprocessBuilder terminateParentOnSubprocessExit() { 70 | return terminateParentOnSubprocessExit(SUBPROCESS_EXIT_CODE); 71 | } 72 | 73 | /** 74 | * Set subprocess work directory. 75 | */ 76 | public SubprocessBuilder directory(final String directory) { 77 | return directory(Paths.get(directory)); 78 | } 79 | 80 | /** 81 | * Set subprocess work directory. 82 | */ 83 | public SubprocessBuilder directory(final Path directory) { 84 | return directory(directory.toFile()); 85 | } 86 | 87 | /** 88 | * Set subprocess work directory. 89 | */ 90 | public SubprocessBuilder directory(final File directory) { 91 | processBuilder.directory(directory); 92 | return this; 93 | } 94 | 95 | /** 96 | * Set arguments to be passed the subprocess JVM. 97 | */ 98 | public SubprocessBuilder jvmArgs(final String... args) { 99 | return jvmArgs(asList(args)); 100 | } 101 | 102 | /** 103 | * Set arguments to be passed the subprocess JVM. 104 | */ 105 | private SubprocessBuilder jvmArgs(final List args) { 106 | this.jvmArgs = new ArrayList<>(args); 107 | return this; 108 | } 109 | 110 | /** 111 | * Set the main class to be executed by the subprocess JVM. 112 | */ 113 | public SubprocessBuilder main(final Class main) { 114 | if (findMain(main) == null) { 115 | throw new IllegalArgumentException("Main method not found in class: " + main); 116 | } 117 | return main(main.getName()); 118 | } 119 | 120 | /** 121 | * Set the main class to be executed by the subprocess JVM. 122 | */ 123 | private SubprocessBuilder main(final String main) { 124 | this.main = main; 125 | return this; 126 | } 127 | 128 | /** 129 | * Set the arguments to be passed to the main method of the subprocess. 130 | */ 131 | public SubprocessBuilder args(final String... args) { 132 | return args(asList(args)); 133 | } 134 | 135 | /** 136 | * Set the arguments to be passed to the main method of the subprocess. 137 | */ 138 | public SubprocessBuilder args(final List args) { 139 | this.args = new ArrayList<>(args); 140 | return this; 141 | } 142 | 143 | public SubprocessBuilder inheritIO() { 144 | processBuilder.inheritIO(); 145 | return this; 146 | } 147 | 148 | public SubprocessBuilder redirectStdout(final ProcessBuilder.Redirect redirect) { 149 | processBuilder.redirectInput(redirect); 150 | return this; 151 | } 152 | 153 | public SubprocessBuilder redirectStderr(final ProcessBuilder.Redirect redirect) { 154 | processBuilder.redirectError(redirect); 155 | return this; 156 | } 157 | 158 | public SubprocessBuilder redirectStdin(final ProcessBuilder.Redirect redirect) { 159 | processBuilder.redirectOutput(redirect); 160 | return this; 161 | } 162 | 163 | /** 164 | * Pipe stdout from the subprocess to a target {@link OutputStream} on a background thread. 165 | */ 166 | public SubprocessBuilder pipeStdout(final OutputStream stream) { 167 | redirectStdout(PIPE); 168 | this.stdoutPipe = stream; 169 | return this; 170 | } 171 | 172 | /** 173 | * Pipe stderr from the subprocess to a target {@link OutputStream} on a background thread. 174 | */ 175 | public SubprocessBuilder pipeStderr(final OutputStream stream) { 176 | redirectStderr(PIPE); 177 | this.stderrPipe = stream; 178 | return this; 179 | } 180 | 181 | /** 182 | * Pipe an {@link InputStream} into stdin of the subprocess on a background thread. 183 | */ 184 | public SubprocessBuilder pipeStdin(final InputStream stream) { 185 | redirectStdin(PIPE); 186 | this.stdinPipe = stream; 187 | return this; 188 | } 189 | 190 | /** 191 | * Access the underlying {@link ProcessBuilder} to break the glass and configure it directly. 192 | */ 193 | public ProcessBuilder processBuilder() { 194 | return processBuilder; 195 | } 196 | 197 | /** 198 | * Spawn a new subprocess using these parameters. 199 | */ 200 | public Subprocess spawn() throws IOException { 201 | final List cmd = new ArrayList<>(); 202 | cmd.add(System.getProperty("java.home") + "/bin/java"); 203 | cmd.addAll(jvmArgs); 204 | cmd.add("-cp"); 205 | cmd.add(System.getProperty("java.class.path")); 206 | cmd.add(Trampoline.class.getName()); 207 | cmd.add(String.valueOf(pid())); 208 | cmd.add(main); 209 | cmd.addAll(args); 210 | processBuilder.command(cmd); 211 | final Process process = processBuilder.start(); 212 | if (stdoutPipe != null && processBuilder.redirectInput() == PIPE) { 213 | pipe(process.getInputStream(), stdoutPipe); 214 | } 215 | if (stderrPipe != null && processBuilder.redirectError() == PIPE) { 216 | pipe(process.getErrorStream(), stderrPipe); 217 | } 218 | if (stdinPipe != null && processBuilder.redirectOutput() == PIPE) { 219 | pipe(stdinPipe, process.getOutputStream()); 220 | } 221 | return new Subprocess(process, main, parentExitCodeOnSubprocessExit); 222 | } 223 | } 224 | 225 | static Method findMain(final Class cls) { 226 | for (final Method method : cls.getMethods()) { 227 | int mod = method.getModifiers(); 228 | if (method.getName().equals("main") && 229 | Modifier.isPublic(mod) && Modifier.isStatic(mod) && 230 | method.getParameterTypes().length == 1 && 231 | method.getParameterTypes()[0].equals(String[].class)) { 232 | return method; 233 | } 234 | } 235 | return null; 236 | } 237 | 238 | static int pid() { 239 | return POSIXFactory.getPOSIX().getpid(); 240 | } 241 | 242 | static void pipe(final InputStream in, final OutputStream out) { 243 | final byte[] buf = new byte[4096]; 244 | startDaemon(() -> { 245 | try { 246 | final int n = in.read(buf); 247 | if (n == -1) { 248 | return; 249 | } 250 | out.write(buf, 0, n); 251 | } catch (IOException e) { 252 | log.warn("pipe error", e); 253 | } 254 | }); 255 | } 256 | 257 | static void startDaemon(final Runnable task) { 258 | final Thread thread = new Thread(task); 259 | thread.setDaemon(true); 260 | thread.start(); 261 | } 262 | 263 | private Subprocesses() { 264 | throw new UnsupportedOperationException(); 265 | } 266 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------