├── .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 | [](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 |
--------------------------------------------------------------------------------