aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-process/src
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2014-09-12 10:19:06 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2014-09-12 10:19:06 +0200
commitaeee283d2109c425828c96bc2952b947cce56566 (patch)
treee00e42fa78c92dd499a231bf9628c6f121919051 /server/sonar-process/src
parent39f52452ee8a3d414dd9f2a4dee3dfffdb082c1d (diff)
downloadsonarqube-aeee283d2109c425828c96bc2952b947cce56566.tar.gz
sonarqube-aeee283d2109c425828c96bc2952b947cce56566.zip
SONAR-4898 refactor process monitoring
Diffstat (limited to 'server/sonar-process/src')
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/AesCipher.java133
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java35
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Cipher.java27
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java70
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Encryption.java64
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java81
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java56
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java71
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/MessageException.java36
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java86
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java31
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java40
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java148
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java53
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java28
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java77
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Props.java120
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/State.java26
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/StopperThread.java57
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/SystemExit.java52
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Terminable.java28
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/package-info.java23
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java185
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java59
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java95
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java59
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java120
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java42
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java51
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java102
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java61
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java224
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java28
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/PropsTest.java135
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java56
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java116
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java81
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt1
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt1
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt3
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt1
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml1
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties212
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties3
-rw-r--r--server/sonar-process/src/test/resources/sonar-dummy-app.jarbin0 -> 854048 bytes
45 files changed, 2978 insertions, 0 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java
new file mode 100644
index 00000000000..204ae1a6b6b
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java
@@ -0,0 +1,133 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.Nullable;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.File;
+import java.io.IOException;
+import java.security.Key;
+import java.security.SecureRandom;
+
+final class AesCipher implements Cipher {
+
+ // Can't be increased because of Java 6 policy files :
+ // https://confluence.terena.org/display/~visser/No+256+bit+ciphers+for+Java+apps
+ // http://java.sun.com/javase/6/webnotes/install/jre/README
+ public static final int KEY_SIZE_IN_BITS = 128;
+
+ private static final String CRYPTO_KEY = "AES";
+
+ /**
+ * Duplication from CoreProperties.ENCRYPTION_SECRET_KEY_PATH
+ */
+ static final String ENCRYPTION_SECRET_KEY_PATH = "sonar.secretKeyPath";
+
+ private String pathToSecretKey;
+
+ AesCipher(@Nullable String pathToSecretKey) {
+ this.pathToSecretKey = pathToSecretKey;
+ }
+
+ @Override
+ public String encrypt(String clearText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, loadSecretFile());
+ return new String(Base64.encodeBase64(cipher.doFinal(clearText.getBytes("UTF-8"))));
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public String decrypt(String encryptedText) {
+ try {
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CRYPTO_KEY);
+ cipher.init(javax.crypto.Cipher.DECRYPT_MODE, loadSecretFile());
+ byte[] cipherData = cipher.doFinal(Base64.decodeBase64(StringUtils.trim(encryptedText)));
+ return new String(cipherData);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * This method checks the existence of the file, but not the validity of the contained key.
+ */
+ boolean hasSecretKey() {
+ String path = getPathToSecretKey();
+ if (StringUtils.isNotBlank(path)) {
+ File file = new File(path);
+ return file.exists() && file.isFile();
+ }
+ return false;
+ }
+
+ private Key loadSecretFile() throws IOException {
+ String path = getPathToSecretKey();
+ return loadSecretFileFromFile(path);
+ }
+
+ Key loadSecretFileFromFile(String path) throws IOException {
+ if (StringUtils.isBlank(path)) {
+ throw new IllegalStateException("Secret key not found. Please set the property " + ENCRYPTION_SECRET_KEY_PATH);
+ }
+ File file = new File(path);
+ if (!file.exists() || !file.isFile()) {
+ throw new IllegalStateException("The property " + ENCRYPTION_SECRET_KEY_PATH + " does not link to a valid file: " + path);
+ }
+ String s = FileUtils.readFileToString(file);
+ if (StringUtils.isBlank(s)) {
+ throw new IllegalStateException("No secret key in the file: " + path);
+ }
+ return new SecretKeySpec(Base64.decodeBase64(StringUtils.trim(s)), CRYPTO_KEY);
+ }
+
+ String generateRandomSecretKey() {
+ try {
+ KeyGenerator keyGen = KeyGenerator.getInstance(CRYPTO_KEY);
+ keyGen.init(KEY_SIZE_IN_BITS, new SecureRandom());
+ SecretKey secretKey = keyGen.generateKey();
+ return new String(Base64.encodeBase64(secretKey.getEncoded()));
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to generate secret key", e);
+ }
+ }
+
+ String getPathToSecretKey() {
+ if (StringUtils.isBlank(pathToSecretKey)) {
+ pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath();
+ }
+ return pathToSecretKey;
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java b/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java
new file mode 100644
index 00000000000..5eb3eecd541
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Base64Cipher.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+import org.apache.commons.codec.binary.Base64;
+
+final class Base64Cipher implements Cipher {
+ @Override
+ public String encrypt(String clearText) {
+ return new String(Base64.encodeBase64(clearText.getBytes()));
+ }
+
+ @Override
+ public String decrypt(String encryptedText) {
+ return new String(Base64.decodeBase64(encryptedText));
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Cipher.java b/server/sonar-process/src/main/java/org/sonar/process/Cipher.java
new file mode 100644
index 00000000000..4c437057757
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Cipher.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+interface Cipher {
+ String encrypt(String clearText);
+
+ String decrypt(String encryptedText);
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java
new file mode 100644
index 00000000000..b4f86457555
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+public final class ConfigurationUtils {
+
+ private ConfigurationUtils() {
+ // Utility class
+ }
+
+ public static Properties interpolateVariables(Properties properties, Map<String, String> variables) {
+ Properties result = new Properties();
+ Enumeration keys = properties.keys();
+ while (keys.hasMoreElements()) {
+ String key = (String) keys.nextElement();
+ String value = (String) properties.get(key);
+ String interpolatedValue = StrSubstitutor.replace(value, variables, "${env:", "}");
+ result.setProperty(key, interpolatedValue);
+ }
+ return result;
+ }
+
+ static Props loadPropsFromCommandLineArgs(String[] args) {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Only a single command-line argument is accepted " +
+ "(absolute path to configuration file)");
+ }
+
+ File propertyFile = new File(args[0]);
+ Properties properties = new Properties();
+ FileReader reader = null;
+ try {
+ reader = new FileReader(propertyFile);
+ properties.load(reader);
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not read properties from file: " + args[0], e);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ FileUtils.deleteQuietly(propertyFile);
+ }
+ return new Props(properties);
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Encryption.java b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java
new file mode 100644
index 00000000000..cca05e6c780
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @since 3.0
+ */
+public final class Encryption {
+
+ private static final String BASE64_ALGORITHM = "b64";
+
+ private static final String AES_ALGORITHM = "aes";
+ private final AesCipher aesCipher;
+
+ private final Map<String, Cipher> ciphers = new HashMap<String, Cipher>();
+ private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)");
+
+ public Encryption(@Nullable String pathToSecretKey) {
+ aesCipher = new AesCipher(pathToSecretKey);
+ ciphers.put(BASE64_ALGORITHM, new Base64Cipher());
+ ciphers.put(AES_ALGORITHM, aesCipher);
+ }
+
+ public boolean isEncrypted(String value) {
+ return value.indexOf('{') == 0 && value.indexOf('}') > 1;
+ }
+
+ public String decrypt(String encryptedText) {
+ Matcher matcher = ENCRYPTED_PATTERN.matcher(encryptedText);
+ if (matcher.matches()) {
+ Cipher cipher = ciphers.get(matcher.group(1).toLowerCase(Locale.ENGLISH));
+ if (cipher != null) {
+ return cipher.decrypt(matcher.group(2));
+ }
+ }
+ return encryptedText;
+ }
+
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java b/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java
new file mode 100644
index 00000000000..c0cf02bcc89
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.remote.JMXServiceURL;
+
+import java.lang.management.ManagementFactory;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+
+public class JmxUtils {
+
+ private JmxUtils() {
+ // only static stuff
+ }
+
+ public static final String DOMAIN = "org.sonar";
+ public static final String NAME_PROPERTY = "name";
+
+ public static final String WEB_SERVER_NAME = "web";
+ public static final String SEARCH_SERVER_NAME = "search";
+
+ public static ObjectName objectName(String name) {
+ try {
+ return new ObjectName(DOMAIN, NAME_PROPERTY, name);
+ } catch (MalformedObjectNameException e) {
+ throw new IllegalStateException("Cannot create ObjectName for " + name, e);
+ }
+ }
+
+ public static void registerMBean(Object mbean, String name) {
+ try {
+ MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
+ ObjectName oName = objectName(name);
+ // Check if already registered in JVM (might run multiple instance in JUnits)
+ if (mbeanServer.isRegistered(oName)) {
+ mbeanServer.unregisterMBean(oName);
+ }
+ mbeanServer.registerMBean(mbean, oName);
+ } catch (RuntimeException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to register JMX MBean named " + name, e);
+ }
+ }
+
+ public static JMXServiceURL serviceUrl(InetAddress host, int port) {
+ String address = host.getHostAddress();
+ if (host instanceof Inet6Address) {
+ // See http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXServiceURL.html
+ // "The host is a host name, an IPv4 numeric host address, or an IPv6 numeric address enclosed in square brackets."
+ address = String.format("[%s]", address);
+ }
+ try {
+ return new JMXServiceURL("rmi", address, port, String.format("/jndi/rmi://%s:%d/jmxrmi", address, port));
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException("JMX url does not look well formed", e);
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
new file mode 100644
index 00000000000..a492ac751ca
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public class Lifecycle {
+
+ private State state = State.INIT;
+
+ public State getState() {
+ return state;
+ }
+
+ public synchronized boolean tryToMoveTo(State to) {
+ if (state.ordinal() < to.ordinal()) {
+ state = to;
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Lifecycle lifecycle = (Lifecycle) o;
+ return state == lifecycle.state;
+ }
+
+ @Override
+ public int hashCode() {
+ return state.hashCode();
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java b/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java
new file mode 100644
index 00000000000..2302a626d2c
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+public class LoopbackAddress {
+
+ private static InetAddress instance;
+
+ private LoopbackAddress() {
+ // only static stuff
+ }
+
+ /**
+ * Quite similar to {@code InetAddress.getLoopbackAddress()} which was introduced in Java 7. This
+ * method aims to support Java 6. It returns an IPv4 address, but not IPv6 in order to
+ * support {@code -Djava.net.preferIPv4Stack=true} which is recommended for Elasticsearch.
+ */
+ public static InetAddress get() {
+ if (instance == null) {
+ try {
+ instance = doGet(NetworkInterface.getNetworkInterfaces());
+ } catch (SocketException e) {
+ throw new IllegalStateException("Fail to browse network interfaces", e);
+ }
+
+ }
+ return instance;
+ }
+
+ static InetAddress doGet(Enumeration<NetworkInterface> ifaces) {
+ InetAddress result = null;
+ while (ifaces.hasMoreElements() && result == null) {
+ NetworkInterface iface = ifaces.nextElement();
+ Enumeration<InetAddress> addresses = iface.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress addr = addresses.nextElement();
+ if (addr.isLoopbackAddress() && addr instanceof Inet4Address) {
+ result = addr;
+ break;
+ }
+ }
+ }
+ if (result == null) {
+ throw new IllegalStateException("Impossible to get a IPv4 loopback address");
+ }
+ return result;
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/MessageException.java b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java
new file mode 100644
index 00000000000..5b86ef66c64
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java
@@ -0,0 +1,36 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public class MessageException extends RuntimeException {
+ public MessageException(String message) {
+ super(message);
+ }
+
+ /**
+ * Does not fill in the stack trace
+ *
+ * @see Throwable#fillInStackTrace()
+ */
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java
new file mode 100644
index 00000000000..2389fa5aa18
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/MinimumViableSystem.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MinimumViableSystem {
+
+ private final Map<String, String> requiredJavaOptions = new HashMap<String, String>();
+
+ public MinimumViableSystem setRequiredJavaOption(String propertyKey, String expectedValue) {
+ requiredJavaOptions.put(propertyKey, expectedValue);
+ return this;
+ }
+
+ /**
+ * Entry point for all checks
+ */
+ public void check() {
+ checkJavaVersion();
+ checkJavaOptions();
+ checkWritableTempDir();
+ }
+
+ /**
+ * Verify that temp directory is writable
+ */
+ private void checkWritableTempDir() {
+ checkWritableDir(System.getProperty("java.io.tmpdir"));
+ }
+
+ void checkWritableDir(String tempPath) {
+ try {
+ File tempFile = File.createTempFile("check", "tmp", new File(tempPath));
+ FileUtils.deleteQuietly(tempFile);
+ } catch (IOException e) {
+ throw new IllegalStateException(String.format("Temp directory is not writable: %s", tempPath), e);
+ }
+ }
+
+ void checkJavaOptions() {
+ for (Map.Entry<String, String> entry : requiredJavaOptions.entrySet()) {
+ String value = System.getProperty(entry.getKey());
+ if (!StringUtils.equals(value, entry.getValue())) {
+ throw new MessageException(String.format(
+ "JVM option '%s' must be set to '%s'. Got '%s'", entry.getKey(), entry.getValue(), StringUtils.defaultString(value)));
+ }
+ }
+ }
+
+ void checkJavaVersion() {
+ String javaVersion = System.getProperty("java.specification.version");
+ checkJavaVersion(javaVersion);
+ }
+
+ void checkJavaVersion(String javaVersion) {
+ if (!javaVersion.startsWith("1.6") && !javaVersion.startsWith("1.7") && !javaVersion.startsWith("1.8")) {
+ // still better than "java.lang.UnsupportedClassVersionError: Unsupported major.minor version 49.0
+ throw new MessageException(String.format("Supported versions of Java are 1.6, 1.7 and 1.8. Got %s.", javaVersion));
+ }
+ }
+
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java b/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java
new file mode 100644
index 00000000000..6ee84d00744
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public interface MonitoredProcess extends Terminable {
+
+ /**
+ * Starts and blocks until ready
+ */
+ void start();
+
+ void awaitTermination();
+
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
new file mode 100644
index 00000000000..074cb8cf5c2
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.net.ServerSocket;
+
+public class NetworkUtils {
+
+ private NetworkUtils() {
+ // only static stuff
+ }
+
+ public static int freePort() {
+ try {
+ ServerSocket s = new ServerSocket(0);
+ int port = s.getLocalPort();
+ s.close();
+ return port;
+ } catch (Exception e) {
+ throw new IllegalStateException("Can not find an open network port", e);
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
new file mode 100644
index 00000000000..79ec79f74b7
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
@@ -0,0 +1,148 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class ProcessEntryPoint implements ProcessMXBean {
+
+ public static final String PROPERTY_PROCESS_KEY = "process.key";
+ public static final String PROPERTY_AUTOKILL_DISABLED = "process.autokill.disabled";
+ public static final String PROPERTY_AUTOKILL_PING_TIMEOUT = "process.autokill.pingTimeout";
+ public static final String PROPERTY_AUTOKILL_PING_INTERVAL = "process.autokill.pingInterval";
+ public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout";
+
+ private final Props props;
+ private final Lifecycle lifecycle = new Lifecycle();
+ private volatile MonitoredProcess monitoredProcess;
+ private volatile long lastPing = 0L;
+ private volatile StopperThread stopperThread;
+ private final SystemExit exit;
+ private Thread shutdownHook = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ exit.setInShutdownHook();
+ terminate();
+ }
+ });
+
+ ProcessEntryPoint(Props props, SystemExit exit) {
+ this.props = props;
+ this.exit = exit;
+ }
+
+ public Props getProps() {
+ return props;
+ }
+
+ /**
+ * Launch process and waits until it's down
+ */
+ public void launch(MonitoredProcess mp) {
+ if (!lifecycle.tryToMoveTo(State.STARTING)) {
+ throw new IllegalStateException("Already started");
+ }
+ monitoredProcess = mp;
+
+ // TODO check if these properties are available in System Info
+ JmxUtils.registerMBean(this, props.nonNullValue(PROPERTY_PROCESS_KEY));
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ if (!props.valueAsBoolean(PROPERTY_AUTOKILL_DISABLED, false)) {
+ // mainly for Java Debugger
+ scheduleAutokill();
+ }
+
+ try {
+ monitoredProcess.start();
+ if (lifecycle.tryToMoveTo(State.STARTED)) {
+ monitoredProcess.awaitTermination();
+ }
+ } catch (Exception ignored) {
+ } finally {
+ terminate();
+ }
+ }
+
+ @Override
+ public boolean isReady() {
+ return lifecycle.getState() == State.STARTED;
+ }
+
+ @Override
+ public void ping() {
+ lastPing = System.currentTimeMillis();
+ }
+
+ /**
+ * Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread})
+ */
+ @Override
+ public void terminate() {
+ if (lifecycle.tryToMoveTo(State.STOPPING)) {
+ stopperThread = new StopperThread(monitoredProcess, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT)));
+ stopperThread.start();
+ }
+ try {
+ // stopperThread is not null for sure
+ // join() does nothing if thread already finished
+ stopperThread.join();
+ lifecycle.tryToMoveTo(State.STOPPED);
+ } catch (InterruptedException e) {
+ // nothing to do, the process is going to be exited
+ }
+ exit.exit(0);
+ }
+
+ private void scheduleAutokill() {
+ final long autokillPingTimeoutMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_TIMEOUT);
+ long autokillPingIntervalMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_INTERVAL);
+ Runnable autokiller = new Runnable() {
+ @Override
+ public void run() {
+ long time = System.currentTimeMillis();
+ if (time - lastPing > autokillPingTimeoutMs) {
+ LoggerFactory.getLogger(getClass()).info(String.format(
+ "Did not receive any ping during %d seconds. Shutting down.", autokillPingTimeoutMs / 1000));
+ terminate();
+ }
+ }
+ };
+ lastPing = System.currentTimeMillis();
+ ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
+ monitor.scheduleWithFixedDelay(autokiller, autokillPingIntervalMs, autokillPingIntervalMs, TimeUnit.MILLISECONDS);
+ }
+
+ State getState() {
+ return lifecycle.getState();
+ }
+
+ Thread getShutdownHook() {
+ return shutdownHook;
+ }
+
+ public static ProcessEntryPoint createForArguments(String[] args) {
+ Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
+ return new ProcessEntryPoint(props, new SystemExit());
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java
new file mode 100644
index 00000000000..dacddd91847
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
+import org.slf4j.LoggerFactory;
+
+public class ProcessLogging {
+
+ private static final String PATH_LOGS_PROPERTY = "sonar.path.logs";
+
+ public void configure(Props props, String logbackXmlResource) {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ try {
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(context);
+ context.reset();
+ context.putProperty(PATH_LOGS_PROPERTY, props.nonNullValue(PATH_LOGS_PROPERTY));
+ doConfigure(configurator, logbackXmlResource);
+ } catch (JoranException ignored) {
+ // StatusPrinter will handle this
+ }
+ StatusPrinter.printInCaseOfErrorsOrWarnings(context);
+
+ }
+
+ /**
+ * Extracted only for unit testing
+ */
+ void doConfigure(JoranConfigurator configurator, String logbackXmlResource) throws JoranException {
+ configurator.doConfigure(getClass().getResource(logbackXmlResource));
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java
new file mode 100644
index 00000000000..3d024d420eb
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public interface ProcessMXBean extends Terminable {
+
+ boolean isReady();
+
+ void ping();
+
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
new file mode 100644
index 00000000000..bdefa116949
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+public class ProcessUtils {
+
+ private ProcessUtils() {
+ // only static stuff
+ }
+
+ /**
+ * Do not abuse to this method. It uses exceptions to get status.
+ * @return false if process is null or terminated, else true.
+ */
+ public static boolean isAlive(@Nullable Process process) {
+ boolean alive = false;
+ if (process != null) {
+ try {
+ process.exitValue();
+ } catch (IllegalThreadStateException ignored) {
+ alive = true;
+ }
+ }
+ return alive;
+ }
+
+ /**
+ * Destroys process (equivalent to kill -9) if alive
+ * @return true if the process was destroyed, false if process is null or already destroyed.
+ */
+ public static boolean destroyQuietly(@Nullable Process process) {
+ boolean destroyed = false;
+ if (isAlive(process)) {
+ try {
+ process.destroy();
+ while (isAlive(process)) {
+ // destroy() sends the signal, it does not wait for the process to be down
+ Thread.sleep(100L);
+ }
+ destroyed = true;
+ } catch (Exception e) {
+ LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process);
+ }
+ }
+ return destroyed;
+ }
+
+ public static void closeStreams(@Nullable Process process) {
+ if (process != null) {
+ IOUtils.closeQuietly(process.getInputStream());
+ IOUtils.closeQuietly(process.getOutputStream());
+ IOUtils.closeQuietly(process.getErrorStream());
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Props.java b/server/sonar-process/src/main/java/org/sonar/process/Props.java
new file mode 100644
index 00000000000..b868702eafc
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java
@@ -0,0 +1,120 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.File;
+import java.util.Properties;
+
+public class Props {
+
+ private final Properties properties;
+ private final Encryption encryption;
+
+ public Props(Properties props) {
+ this.properties = props;
+ this.encryption = new Encryption(props.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH));
+ }
+
+ public boolean contains(String key) {
+ return properties.containsKey(key);
+ }
+
+ @CheckForNull
+ public String value(String key) {
+ String value = properties.getProperty(key);
+ if (value != null && encryption.isEncrypted(value)) {
+ value = encryption.decrypt(value);
+ }
+ return value;
+ }
+
+ public String nonNullValue(String key) {
+ String value = value(key);
+ if (value == null) {
+ throw new IllegalArgumentException("Missing property: " + key);
+ }
+ return value;
+ }
+
+ @CheckForNull
+ public String value(String key, @Nullable String defaultValue) {
+ String s = value(key);
+ return s == null ? defaultValue : s;
+ }
+
+ public boolean valueAsBoolean(String key) {
+ String s = value(key);
+ return s != null && Boolean.parseBoolean(s);
+ }
+
+ public boolean valueAsBoolean(String key, boolean defaultValue) {
+ String s = value(key);
+ return s != null ? Boolean.parseBoolean(s) : defaultValue;
+ }
+
+ public File nonNullValueAsFile(String key) {
+ String s = value(key);
+ if (s == null) {
+ throw new IllegalArgumentException("Property " + key + " is missing");
+ }
+ return new File(s);
+ }
+
+ @CheckForNull
+ public Integer valueAsInt(String key) {
+ String s = value(key);
+ if (s != null && !"".equals(s)) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Value of property " + key + " is not an integer: " + s, e);
+ }
+ }
+ return null;
+ }
+
+ public int valueAsInt(String key, int defaultValue) {
+ Integer i = valueAsInt(key);
+ return i == null ? defaultValue : i;
+ }
+
+ public Properties rawProperties() {
+ return properties;
+ }
+
+ public Props set(String key, @Nullable String value) {
+ if (value != null) {
+ properties.setProperty(key, value);
+ }
+ return this;
+ }
+
+ public void setDefault(String key, String value) {
+ String s = properties.getProperty(key);
+ if (StringUtils.isBlank(s)) {
+ properties.setProperty(key, value);
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/State.java b/server/sonar-process/src/main/java/org/sonar/process/State.java
new file mode 100644
index 00000000000..9d773d2bc23
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/State.java
@@ -0,0 +1,26 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public enum State {
+
+ INIT, STARTING, STARTED, STOPPING, STOPPED
+
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
new file mode 100644
index 00000000000..2d0c6734b30
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Gracefully stops process, but exits JVM if too long
+ */
+class StopperThread extends Thread {
+
+ private final Terminable terminable;
+ private final long terminationTimeout;
+
+ StopperThread(Terminable terminable, long terminationTimeout) {
+ super("Stopper");
+ this.terminable = terminable;
+ this.terminationTimeout = terminationTimeout;
+ }
+
+ @Override
+ public void run() {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ Future future = executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ terminable.terminate();
+ }
+ });
+ try {
+ future.get(terminationTimeout, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ future.cancel(true);
+ executor.shutdownNow();
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java b/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java
new file mode 100644
index 00000000000..8f12a210633
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/SystemExit.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Calls {@link System#exit(int)} except from shutdown hooks, to prevent
+ * deadlocks. See http://stackoverflow.com/a/19552359/229031
+ */
+public class SystemExit {
+
+ private final AtomicBoolean inShutdownHook = new AtomicBoolean(false);
+
+ public void exit(int code) {
+ if (!inShutdownHook.get()) {
+ doExit(code);
+ }
+ }
+
+ public boolean isInShutdownHook() {
+ return inShutdownHook.get();
+ }
+
+ /**
+ * Declarative approach. I don't know how to get this lifecycle state from Java API.
+ */
+ public void setInShutdownHook() {
+ inShutdownHook.set(true);
+ }
+
+ void doExit(int code) {
+ System.exit(code);
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Terminable.java b/server/sonar-process/src/main/java/org/sonar/process/Terminable.java
new file mode 100644
index 00000000000..a8670609fe2
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Terminable.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+/**
+ * This term "terminate" is used in order to not conflict with {@link Thread#stop()}.
+ */
+public interface Terminable {
+
+ void terminate();
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/package-info.java
new file mode 100644
index 00000000000..09da5ce266d
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.process;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java b/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java
new file mode 100644
index 00000000000..8350eafaa3e
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/AesCipherTest.java
@@ -0,0 +1,185 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+import com.google.common.io.Resources;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import javax.crypto.BadPaddingException;
+import java.io.File;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+
+public class AesCipherTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void generateRandomSecretKey() {
+ AesCipher cipher = new AesCipher(null);
+
+ String key = cipher.generateRandomSecretKey();
+
+ assertThat(StringUtils.isNotBlank(key)).isTrue();
+ assertThat(Base64.isArrayByteBase64(key.getBytes())).isTrue();
+ }
+
+ @Test
+ public void encrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ String encryptedText = cipher.encrypt("this is a secret");
+
+ assertThat(StringUtils.isNotBlank(encryptedText)).isTrue();
+ assertThat(Base64.isArrayByteBase64(encryptedText.getBytes())).isTrue();
+ }
+
+ @Test
+ public void encrypt_bad_key() throws Exception {
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("Invalid AES key");
+
+ AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt"));
+
+ cipher.encrypt("this is a secret");
+ }
+
+ @Test
+ public void decrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ // the following value has been encrypted with the key /org/sonar/api/config/AesCipherTest/aes_secret_key.txt
+ String clearText = cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+
+ assertThat(clearText).isEqualTo("this is a secret");
+ }
+
+ @Test
+ public void decrypt_bad_key() throws Exception {
+ AesCipher cipher = new AesCipher(getPath("bad_secret_key.txt"));
+
+ try {
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isInstanceOf(InvalidKeyException.class);
+ }
+ }
+
+ @Test
+ public void decrypt_other_key() throws Exception {
+ AesCipher cipher = new AesCipher(getPath("other_secret_key.txt"));
+
+ try {
+ // text encrypted with another key
+ cipher.decrypt("9mx5Zq4JVyjeChTcVjEide4kWCwusFl7P2dSVXtg9IY=");
+ fail();
+
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isInstanceOf(BadPaddingException.class);
+ }
+ }
+
+ @Test
+ public void encryptThenDecrypt() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ assertThat(cipher.decrypt(cipher.encrypt("foo"))).isEqualTo("foo");
+ }
+
+ @Test
+ public void testDefaultPathToSecretKey() {
+ AesCipher cipher = new AesCipher(null);
+
+ String path = cipher.getPathToSecretKey();
+
+ assertThat(StringUtils.isNotBlank(path)).isTrue();
+ assertThat(new File(path).getName()).isEqualTo("sonar-secret.txt");
+ }
+
+ @Test
+ public void loadSecretKeyFromFile() throws Exception {
+ AesCipher cipher = new AesCipher(null);
+ Key secretKey = cipher.loadSecretFileFromFile(pathToSecretKey());
+ assertThat(secretKey.getAlgorithm()).isEqualTo("AES");
+ assertThat(secretKey.getEncoded().length).isGreaterThan(10);
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_trim_content() throws Exception {
+ String path = getPath("non_trimmed_secret_key.txt");
+ AesCipher cipher = new AesCipher(null);
+
+ Key secretKey = cipher.loadSecretFileFromFile(path);
+
+ assertThat(secretKey.getAlgorithm()).isEqualTo("AES");
+ assertThat(secretKey.getEncoded().length).isGreaterThan(10);
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_file_does_not_exist() throws Exception {
+ thrown.expect(IllegalStateException.class);
+
+ AesCipher cipher = new AesCipher(null);
+ cipher.loadSecretFileFromFile("/file/does/not/exist");
+ }
+
+ @Test
+ public void loadSecretKeyFromFile_no_property() throws Exception {
+ thrown.expect(IllegalStateException.class);
+
+ AesCipher cipher = new AesCipher(null);
+ cipher.loadSecretFileFromFile(null);
+ }
+
+ @Test
+ public void hasSecretKey() throws Exception {
+ AesCipher cipher = new AesCipher(pathToSecretKey());
+
+ assertThat(cipher.hasSecretKey()).isTrue();
+ }
+
+ @Test
+ public void doesNotHaveSecretKey() throws Exception {
+ AesCipher cipher = new AesCipher("/my/twitter/id/is/SimonBrandhof");
+
+ assertThat(cipher.hasSecretKey()).isFalse();
+ }
+
+ private static String getPath(String file) {
+ return Resources.getResource(AesCipherTest.class, "AesCipherTest/" + file).getPath();
+ }
+
+ private static String pathToSecretKey() throws Exception {
+ return getPath("aes_secret_key.txt");
+ }
+
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java b/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java
new file mode 100644
index 00000000000..2045cd4516d
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ServerSocket;
+
+public abstract class BaseProcessTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ public static final String DUMMY_OK_APP = "org.sonar.application.DummyOkProcess";
+
+ int freePort;
+ File dummyAppJar;
+ Process proc;
+
+ @Before
+ public void setup() throws IOException {
+ ServerSocket socket = new ServerSocket(0);
+ freePort = socket.getLocalPort();
+ socket.close();
+
+ dummyAppJar = FileUtils.toFile(getClass().getResource("/sonar-dummy-app.jar"));
+ }
+
+ @After
+ public void tearDown() {
+ if (proc != null) {
+ ProcessUtils.destroyQuietly(proc);
+ }
+ }
+
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java
new file mode 100644
index 00000000000..de928b93850
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class ConfigurationUtilsTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void shouldInterpolateVariables() {
+ Properties input = new Properties();
+ input.setProperty("hello", "world");
+ input.setProperty("url", "${env:SONAR_JDBC_URL}");
+ input.setProperty("do_not_change", "${SONAR_JDBC_URL}");
+ Map<String, String> variables = Maps.newHashMap();
+ variables.put("SONAR_JDBC_URL", "jdbc:h2:mem");
+
+ Properties output = ConfigurationUtils.interpolateVariables(input, variables);
+
+ assertThat(output).hasSize(3);
+ assertThat(output.getProperty("hello")).isEqualTo("world");
+ assertThat(output.getProperty("url")).isEqualTo("jdbc:h2:mem");
+ assertThat(output.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}");
+
+ // input is not changed
+ assertThat(input).hasSize(3);
+ assertThat(input.getProperty("hello")).isEqualTo("world");
+ assertThat(input.getProperty("url")).isEqualTo("${env:SONAR_JDBC_URL}");
+ assertThat(input.getProperty("do_not_change")).isEqualTo("${SONAR_JDBC_URL}");
+ }
+
+ @Test
+ public void loadPropsFromCommandLineArgs_missing_argument() throws Exception {
+ try {
+ ConfigurationUtils.loadPropsFromCommandLineArgs(new String[0]);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).startsWith("Only a single command-line argument is accepted");
+ }
+ }
+
+ @Test
+ public void loadPropsFromCommandLineArgs_load_properties_from_file() throws Exception {
+ File propsFile = temp.newFile();
+ FileUtils.write(propsFile, "foo=bar");
+
+ Props result = ConfigurationUtils.loadPropsFromCommandLineArgs(new String[] {propsFile.getAbsolutePath()});
+ assertThat(result.value("foo")).isEqualTo("bar");
+ assertThat(result.rawProperties()).hasSize(1);
+ }
+
+ @Test
+ public void loadPropsFromCommandLineArgs_file_does_not_exist() throws Exception {
+ File propsFile = temp.newFile();
+ FileUtils.deleteQuietly(propsFile);
+
+ try {
+ ConfigurationUtils.loadPropsFromCommandLineArgs(new String[]{propsFile.getAbsolutePath()});
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Could not read properties from file: " + propsFile.getAbsolutePath());
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java b/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java
new file mode 100644
index 00000000000..0c11856b0fa
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/EncryptionTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.process;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class EncryptionTest {
+
+ @Test
+ public void isEncrypted() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.isEncrypted("{aes}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{b64}ADASDASAD"), is(true));
+ assertThat(encryption.isEncrypted("{abc}ADASDASAD"), is(true));
+
+ assertThat(encryption.isEncrypted("{}"), is(false));
+ assertThat(encryption.isEncrypted("{foo"), is(false));
+ assertThat(encryption.isEncrypted("foo{aes}"), is(false));
+ }
+
+ @Test
+ public void decrypt() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("{b64}Zm9v"), is("foo"));
+ }
+
+ @Test
+ public void decrypt_unknown_algorithm() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("{xxx}Zm9v"), is("{xxx}Zm9v"));
+ }
+
+ @Test
+ public void decrypt_uncrypted_text() {
+ Encryption encryption = new Encryption(null);
+ assertThat(encryption.decrypt("foo"), is("foo"));
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java
new file mode 100644
index 00000000000..599ea5d7a30
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java
@@ -0,0 +1,120 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.remote.JMXServiceURL;
+
+import java.lang.management.ManagementFactory;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class JmxUtilsTest {
+
+ class MyBean implements ProcessMXBean {
+
+
+ @Override
+ public void terminate() {
+
+ }
+
+ @Override
+ public void ping() {
+
+ }
+
+ @Override
+ public boolean isReady() {
+ return true;
+ }
+ }
+
+ @Test
+ public void construct_jmx_objectName() throws Exception {
+ MyBean mxBean = new MyBean();
+ ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName());
+ assertThat(objectName).isNotNull();
+ assertThat(objectName.getDomain()).isEqualTo(JmxUtils.DOMAIN);
+ assertThat(objectName.getKeyProperty(JmxUtils.NAME_PROPERTY)).isEqualTo(mxBean.getClass().getSimpleName());
+ }
+
+ @Test
+ public void fail_jmx_objectName() throws Exception {
+ try {
+ JmxUtils.objectName(":");
+ fail();
+ } catch (Exception e) {
+ assertThat(e.getMessage()).isEqualTo("Cannot create ObjectName for :");
+ }
+ }
+
+ @Test
+ public void testRegisterMBean() throws Exception {
+ // 0 Get mbServer and create out test MXBean
+ MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
+ MyBean mxBean = new MyBean();
+ ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName());
+
+ // 1 assert that mxBean gets registered
+ assertThat(mbeanServer.isRegistered(objectName)).isFalse();
+ JmxUtils.registerMBean(mxBean, mxBean.getClass().getSimpleName());
+ assertThat(mbeanServer.isRegistered(objectName)).isTrue();
+ }
+
+ @Test
+ public void serviceUrl_ipv4() throws Exception {
+ JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet4Address.class), 1234);
+ assertThat(url).isNotNull();
+ assertThat(url.getPort()).isEqualTo(1234);
+ }
+
+ @Test
+ public void serviceUrl_ipv6() throws Exception {
+ JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet6Address.class), 1234);
+ assertThat(url).isNotNull();
+ assertThat(url.getPort()).isEqualTo(1234);
+ }
+
+ private static InetAddress ip(Class inetAddressClass) throws SocketException {
+ Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
+ while (ifaces.hasMoreElements()) {
+ NetworkInterface iface = ifaces.nextElement();
+ Enumeration<InetAddress> addresses = iface.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress addr = addresses.nextElement();
+ if (addr.getClass().isAssignableFrom(inetAddressClass)) {
+ return addr;
+ }
+ }
+ }
+ throw new IllegalStateException("no ipv4 address");
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
new file mode 100644
index 00000000000..23886125212
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class LifecycleTest {
+
+ @Test
+ public void equals_and_hashcode() throws Exception {
+ Lifecycle init = new Lifecycle();
+ assertThat(init.equals(init)).isTrue();
+ assertThat(init.equals(new Lifecycle())).isTrue();
+ assertThat(init.equals("INIT")).isFalse();
+ assertThat(init.equals(null)).isFalse();
+ assertThat(init.hashCode()).isEqualTo(new Lifecycle().hashCode());
+
+ // different state
+ Lifecycle stopping = new Lifecycle();
+ stopping.tryToMoveTo(State.STOPPING);
+ assertThat(stopping).isNotEqualTo(init);
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java b/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java
new file mode 100644
index 00000000000..6a8819c3a81
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import com.google.common.collect.Iterators;
+import org.junit.Test;
+
+import java.net.NetworkInterface;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class LoopbackAddressTest {
+
+ @Test
+ public void get() throws Exception {
+ assertThat(LoopbackAddress.get()).isNotNull();
+ assertThat(LoopbackAddress.get().isLoopbackAddress()).isTrue();
+ assertThat(LoopbackAddress.get().getHostAddress()).isNotNull();
+ }
+
+ @Test
+ public void fail_to_get_loopback_address() throws Exception {
+ Enumeration<NetworkInterface> ifaces = Iterators.asEnumeration(Collections.<NetworkInterface>emptyList().iterator());
+ try {
+ LoopbackAddress.doGet(ifaces);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Impossible to get a IPv4 loopback address");
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java b/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java
new file mode 100644
index 00000000000..83841c79952
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/MinimumViableSystemTest.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.fest.assertions.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+import static org.fest.assertions.Fail.fail;
+
+public class MinimumViableSystemTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ /**
+ * Verifies that all checks can be verified without error.
+ * Test environment does not necessarily follows all checks.
+ */
+ @Test
+ public void check() throws Exception {
+ MinimumViableSystem mve = new MinimumViableSystem();
+
+ try {
+ mve.check();
+ // ok
+ } catch (MessageException e) {
+ // also ok. All other exceptions are errors.
+ }
+ }
+
+ @Test
+ public void checkJavaVersion() throws Exception {
+ MinimumViableSystem mve = new MinimumViableSystem();
+
+ // yes, sources are compiled with a supported Java version!
+ mve.checkJavaVersion();
+ mve.checkJavaVersion("1.6");
+
+ try {
+ mve.checkJavaVersion("1.9");
+ fail();
+ } catch (MessageException e) {
+ Assertions.assertThat(e).hasMessage("Supported versions of Java are 1.6, 1.7 and 1.8. Got 1.9.");
+ }
+ }
+
+ @Test
+ public void checkJavaOption() throws Exception {
+ String key = "MinimumViableEnvironmentTest.test.prop";
+ MinimumViableSystem mve = new MinimumViableSystem()
+ .setRequiredJavaOption(key, "true");
+
+ try {
+ System.setProperty(key, "false");
+ mve.checkJavaOptions();
+ fail();
+ } catch (MessageException e) {
+ Assertions.assertThat(e).hasMessage("JVM option '" + key + "' must be set to 'true'. Got 'false'");
+ }
+
+ System.setProperty(key, "true");
+ mve.checkJavaOptions();
+ // do not fail
+ }
+
+ @Test
+ public void checkWritableTempDir() throws Exception {
+ File dir = temp.newFolder();
+ MinimumViableSystem mve = new MinimumViableSystem();
+
+ mve.checkWritableDir(dir.getAbsolutePath());
+
+ dir.delete();
+ try {
+ mve.checkWritableDir(dir.getAbsolutePath());
+ fail();
+ } catch (IllegalStateException e) {
+ Assertions.assertThat(e).hasMessage("Temp directory is not writable: " + dir.getAbsolutePath());
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java
new file mode 100644
index 00000000000..09f6a597209
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import java.net.ServerSocket;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class NetworkUtilsTest {
+
+
+ @Test
+ public void find_free_port() throws Exception {
+ int port = NetworkUtils.freePort();
+ assertThat(port).isGreaterThan(1024);
+ }
+
+ @Test
+ public void find_multiple_free_port() throws Exception {
+ int port1 = NetworkUtils.freePort();
+ int port2 = NetworkUtils.freePort();
+
+ assertThat(port1).isGreaterThan(1024);
+ assertThat(port2).isGreaterThan(1024);
+
+ assertThat(port1).isNotSameAs(port2);
+ }
+
+ @Test
+ public void find_multiple_free_non_adjacent_port() throws Exception {
+ int port1 = NetworkUtils.freePort();
+
+ ServerSocket socket = new ServerSocket(port1 + 1);
+
+ int port2 = NetworkUtils.freePort();
+
+ assertThat(port1).isGreaterThan(1024);
+ assertThat(port2).isGreaterThan(1024);
+
+ assertThat(port1).isNotSameAs(port2);
+ }
+} \ No newline at end of file
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
new file mode 100644
index 00000000000..55bf8d679d1
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
@@ -0,0 +1,224 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.Timeout;
+import org.sonar.process.test.StandardProcess;
+
+import java.io.File;
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.mock;
+
+public class ProcessEntryPointTest {
+
+ SystemExit exit = mock(SystemExit.class);
+
+ /**
+ * Safeguard
+ */
+ @Rule
+ public Timeout timeout = new Timeout(10000);
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void load_properties_from_file() throws Exception {
+ File propsFile = temp.newFile();
+ FileUtils.write(propsFile, "sonar.foo=bar");
+
+ ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[]{propsFile.getAbsolutePath()});
+ assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar");
+ }
+
+ @Test
+ public void test_initial_state() throws Exception {
+ Props props = new Props(new Properties());
+ ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+
+ assertThat(entryPoint.getProps()).isSameAs(props);
+ assertThat(entryPoint.isReady()).isFalse();
+ assertThat(entryPoint.getState()).isEqualTo(State.INIT);
+
+ // do not fail
+ entryPoint.ping();
+ }
+
+ @Test
+ public void fail_to_launch_if_missing_monitor_properties() throws Exception {
+ Props props = new Props(new Properties());
+ ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+
+ StandardProcess process = new StandardProcess();
+ try {
+ entryPoint.launch(process);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Missing property: process.key");
+ assertThat(process.getState()).isEqualTo(State.INIT);
+ }
+ }
+
+ @Test
+ public void fail_to_launch_multiple_times() throws Exception {
+ Props props = new Props(new Properties());
+ props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
+ props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
+ ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+
+ entryPoint.launch(new NoopProcess());
+ try {
+ entryPoint.launch(new NoopProcess());
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Already started");
+ }
+ }
+
+ @Test
+ public void launch_then_request_graceful_termination() throws Exception {
+ Props props = new Props(new Properties());
+ props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
+ props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
+ final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+ final StandardProcess process = new StandardProcess();
+
+ Thread runner = new Thread() {
+ @Override
+ public void run() {
+ // starts and waits until terminated
+ entryPoint.launch(process);
+ }
+ };
+ runner.start();
+
+ while (process.getState() != State.STARTED) {
+ Thread.sleep(10L);
+ }
+
+ // requests for termination -> waits until down
+ // Should terminate before the timeout of 30s
+ entryPoint.terminate();
+
+ assertThat(process.getState()).isEqualTo(State.STOPPED);
+ }
+
+ @Test
+ public void autokill_if_no_pings() throws Exception {
+ Props props = new Props(new Properties());
+ props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test");
+ props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, "5");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, "1");
+ final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+ final StandardProcess process = new StandardProcess();
+
+ entryPoint.launch(process);
+
+ assertThat(process.getState()).isEqualTo(State.STOPPED);
+ }
+
+ @Test
+ public void terminate_if_unexpected_shutdown() throws Exception {
+ Props props = new Props(new Properties());
+ props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
+ props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
+ final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+ final StandardProcess process = new StandardProcess();
+
+ Thread runner = new Thread() {
+ @Override
+ public void run() {
+ // starts and waits until terminated
+ entryPoint.launch(process);
+ }
+ };
+ runner.start();
+ while (process.getState() != State.STARTED) {
+ Thread.sleep(10L);
+ }
+
+ // emulate signal to shutdown process
+ entryPoint.getShutdownHook().start();
+ while (process.getState() != State.STOPPED) {
+ Thread.sleep(10L);
+ }
+ // exit before test timeout, ok !
+ }
+
+ @Test
+ public void terminate_if_startup_error() throws Exception {
+ Props props = new Props(new Properties());
+ props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo");
+ props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true");
+ props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000");
+ final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit);
+ final MonitoredProcess process = new StartupErrorProcess();
+
+ entryPoint.launch(process);
+ assertThat(entryPoint.getState()).isEqualTo(State.STOPPED);
+ }
+
+ private static class NoopProcess implements MonitoredProcess {
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void awaitTermination() {
+
+ }
+
+ @Override
+ public void terminate() {
+
+ }
+ }
+
+ private static class StartupErrorProcess implements MonitoredProcess {
+
+ @Override
+ public void start() {
+ throw new IllegalStateException("ERROR");
+ }
+
+ @Override
+ public void awaitTermination() {
+
+ }
+
+ @Override
+ public void terminate() {
+
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
new file mode 100644
index 00000000000..6f93126516e
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ProcessUtilsTest {
+
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java b/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java
new file mode 100644
index 00000000000..5d283b44f8f
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java
@@ -0,0 +1,135 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class PropsTest {
+
+ @Test
+ public void of() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "bar");
+ Props props = new Props(p);
+
+ assertThat(props.value("foo")).isEqualTo("bar");
+ assertThat(props.value("foo", "default value")).isEqualTo("bar");
+ assertThat(props.value("unknown")).isNull();
+ assertThat(props.value("unknown", "default value")).isEqualTo("default value");
+ }
+
+ @Test
+ public void intOf() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "33");
+ p.setProperty("blank", "");
+ Props props = new Props(p);
+
+ assertThat(props.valueAsInt("foo")).isEqualTo(33);
+ assertThat(props.valueAsInt("foo", 44)).isEqualTo(33);
+ assertThat(props.valueAsInt("blank")).isNull();
+ assertThat(props.valueAsInt("blank", 55)).isEqualTo(55);
+ assertThat(props.valueAsInt("unknown")).isNull();
+ assertThat(props.valueAsInt("unknown", 44)).isEqualTo(44);
+ }
+
+ @Test
+ public void intOf_not_integer() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "bar");
+ Props props = new Props(p);
+
+ try {
+ props.valueAsInt("foo");
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Value of property foo is not an integer: bar");
+ }
+ }
+
+ @Test
+ public void booleanOf() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "True");
+ p.setProperty("bar", "false");
+ Props props = new Props(p);
+
+ assertThat(props.valueAsBoolean("foo")).isTrue();
+ assertThat(props.valueAsBoolean("bar")).isFalse();
+ assertThat(props.valueAsBoolean("unknown")).isFalse();
+ }
+
+ @Test
+ public void booleanOf_default_value() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "true");
+ p.setProperty("bar", "false");
+ Props props = new Props(p);
+
+ assertThat(props.valueAsBoolean("unset", false)).isFalse();
+ assertThat(props.valueAsBoolean("unset", true)).isTrue();
+ assertThat(props.valueAsBoolean("foo", false)).isTrue();
+ assertThat(props.valueAsBoolean("bar", true)).isFalse();
+ }
+
+ @Test
+ public void setDefault() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "foo_value");
+ Props props = new Props(p);
+ props.setDefault("foo", "foo_def");
+ props.setDefault("bar", "bar_def");
+
+ assertThat(props.value("foo")).isEqualTo("foo_value");
+ assertThat(props.value("bar")).isEqualTo("bar_def");
+ assertThat(props.value("other")).isNull();
+ }
+
+ @Test
+ public void set() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("foo", "old_foo");
+ Props props = new Props(p);
+ props.set("foo", "new_foo");
+ props.set("bar", "new_bar");
+
+ assertThat(props.value("foo")).isEqualTo("new_foo");
+ assertThat(props.value("bar")).isEqualTo("new_bar");
+ }
+
+ @Test
+ public void raw_properties() throws Exception {
+ Properties p = new Properties();
+ p.setProperty("encrypted_prop", "{aes}abcde");
+ p.setProperty("clear_prop", "foo");
+ Props props = new Props(p);
+
+ assertThat(props.rawProperties()).hasSize(2);
+ // do not decrypt
+ assertThat(props.rawProperties().get("encrypted_prop")).isEqualTo("{aes}abcde");
+ assertThat(props.rawProperties().get("clear_prop")).isEqualTo("foo");
+
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java b/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java
new file mode 100644
index 00000000000..e02b07cf555
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/SystemExitTest.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class SystemExitTest {
+
+ @Test
+ public void do_not_exit_if_in_shutdown_hook() throws Exception {
+ SystemExit systemExit = new SystemExit();
+
+ systemExit.setInShutdownHook();
+ assertThat(systemExit.isInShutdownHook()).isTrue();
+
+ systemExit.exit(0);
+ // still there
+ }
+
+ @Test
+ public void exit_if_not_in_shutdown_hook() throws Exception {
+ final AtomicInteger got = new AtomicInteger();
+ SystemExit systemExit = new SystemExit() {
+ @Override
+ void doExit(int code) {
+ got.set(code);
+ }
+ };
+
+ assertThat(systemExit.isInShutdownHook()).isFalse();
+ systemExit.exit(1);
+
+ assertThat(got.get()).isEqualTo(1);
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
new file mode 100644
index 00000000000..307abed6d0b
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
@@ -0,0 +1,116 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.test;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.sonar.process.MonitoredProcess;
+import org.sonar.process.ProcessEntryPoint;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Http server used for testing (see MonitorTest). It accepts HTTP commands /ping and /kill to hardly exit.
+ * It also pushes status to temp files, so test can verify what was really done (when server went ready state and
+ * if it was gracefully terminated)
+ */
+public class HttpProcess implements MonitoredProcess {
+
+ private final Server server;
+ // temp dir is specific to this process
+ private final File tempDir = new File(System.getProperty("java.io.tmpdir"));
+
+ public HttpProcess(int httpPort) {
+ server = new Server(httpPort);
+ }
+
+ @Override
+ public void start() {
+ writeTimeToFile("startingAt");
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ context.setClassLoader(Thread.currentThread().getContextClassLoader());
+ server.setHandler(context);
+ context.setHandler(new AbstractHandler() {
+ @Override
+ public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
+ if ("/ping".equals(target)) {
+ request.setHandled(true);
+ httpServletResponse.getWriter().print("ping");
+ } else if ("/kill".equals(target)) {
+ writeTimeToFile("killedAt");
+ System.exit(0);
+ }
+ }
+ });
+ try {
+ server.start();
+ while (!server.isStarted()) {
+ Thread.sleep(100L);
+ }
+ writeTimeToFile("readyAt");
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to start Jetty", e);
+ }
+ }
+
+ @Override
+ public void awaitTermination() {
+ try {
+ server.join();
+ } catch (InterruptedException ignore) {
+
+ }
+ }
+
+ @Override
+ public void terminate() {
+ try {
+ if (!server.isStopped()) {
+ server.stop();
+ writeTimeToFile("terminatedAt");
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to stop Jetty", e);
+ }
+ }
+
+ private void writeTimeToFile(String filename) {
+ try {
+ FileUtils.write(new File(tempDir, filename), String.valueOf(System.currentTimeMillis()));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args);
+ entryPoint.launch(new HttpProcess(entryPoint.getProps().valueAsInt("httpPort")));
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
new file mode 100644
index 00000000000..121784f633b
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.test;
+
+import org.sonar.process.MonitoredProcess;
+import org.sonar.process.ProcessEntryPoint;
+import org.sonar.process.State;
+
+public class StandardProcess implements MonitoredProcess {
+
+ private State state = State.INIT;
+
+ private final Thread daemon = new Thread() {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ Thread.sleep(100L);
+ }
+ } catch (InterruptedException e) {
+ // return
+ }
+ }
+ };
+
+ /**
+ * Blocks until started()
+ */
+ @Override
+ public void start() {
+ state = State.STARTING;
+ daemon.start();
+ state = State.STARTED;
+ }
+
+ @Override
+ public void awaitTermination() {
+ try {
+ daemon.join();
+ } catch (InterruptedException e) {
+ // interrupted by call to terminate()
+ }
+ }
+
+ /**
+ * Blocks until stopped
+ */
+ @Override
+ public void terminate() {
+ state = State.STOPPING;
+ daemon.interrupt();
+ state = State.STOPPED;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public static void main(String[] args) {
+ ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args);
+ entryPoint.launch(new StandardProcess());
+ System.exit(0);
+ }
+}
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt
new file mode 100644
index 00000000000..65b98c522da
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/aes_secret_key.txt
@@ -0,0 +1 @@
+0PZz+G+f8mjr3sPn4+AhHg== \ No newline at end of file
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt
new file mode 100644
index 00000000000..b33e179e5c8
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/bad_secret_key.txt
@@ -0,0 +1 @@
+badbadbad== \ No newline at end of file
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt
new file mode 100644
index 00000000000..ab83e4adc03
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/non_trimmed_secret_key.txt
@@ -0,0 +1,3 @@
+
+ 0PZz+G+f8mjr3sPn4+AhHg==
+
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt
new file mode 100644
index 00000000000..23f5ecf5104
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/AesCipherTest/other_secret_key.txt
@@ -0,0 +1 @@
+IBxEUxZ41c8XTxyaah1Qlg== \ No newline at end of file
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml b/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
new file mode 100644
index 00000000000..298193e01fa
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
@@ -0,0 +1 @@
+<configuration/>
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties b/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties
new file mode 100644
index 00000000000..1577a214b3b
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties
@@ -0,0 +1,212 @@
+# This file must contain only ISO 8859-1 characters
+# see http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Properties.html#load(java.io.InputStream)
+#
+# To use an environment variable, use the following syntax : ${env:NAME_OF_ENV_VARIABLE}
+# For example:
+# sonar.jdbc.url= ${env:SONAR_JDBC_URL}
+#
+#
+# See also the file conf/wrapper.conf for JVM advanced settings
+
+
+
+#--------------------------------------------------------------------------------------------------
+# DATABASE
+#
+# IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only.
+# Please use a production-ready database. Supported databases are MySQL, Oracle, PostgreSQL
+# and Microsoft SQLServer.
+
+# Permissions to create tables, indices and triggers must be granted to JDBC user.
+# The schema must be created first.
+sonar.jdbc.username=sonar
+sonar.jdbc.password=sonar
+
+#----- Embedded database H2
+# Note: it does not accept connections from remote hosts, so the
+# SonarQube server and the maven plugin must be executed on the same host.
+
+# Comment the following line to deactivate the default embedded database.
+sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar
+
+# directory containing H2 database files. By default it's the /data directory in the SonarQube installation.
+#sonar.embeddedDatabase.dataDir=
+# H2 embedded database server listening port, defaults to 9092
+#sonar.embeddedDatabase.port=9092
+
+
+#----- MySQL 5.x
+# Comment the embedded database and uncomment the following line to use MySQL
+#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true
+
+
+#----- Oracle 10g/11g
+# To connect to Oracle database:
+#
+# - It's recommended to use the latest version of the JDBC driver (ojdbc6.jar).
+# Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html
+# - Copy the driver to the directory extensions/jdbc-driver/oracle/
+# - If you need to set the schema, please refer to http://jira.codehaus.org/browse/SONAR-5000
+# - Comment the embedded database and uncomment the following line:
+#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE
+
+
+#----- PostgreSQL 8.x/9.x
+# Comment the embedded database and uncomment the following property to use PostgreSQL.
+# If you don't use the schema named "public", please refer to http://jira.codehaus.org/browse/SONAR-5000
+#sonar.jdbc.url=jdbc:postgresql://localhost/sonar
+
+
+#----- Microsoft SQLServer
+# The Jtds open source driver is available in extensions/jdbc-driver/mssql. More details on http://jtds.sourceforge.net
+#sonar.jdbc.url=jdbc:jtds:sqlserver://localhost/sonar;SelectMethod=Cursor
+
+
+#----- Connection pool settings
+sonar.jdbc.maxActive=20
+sonar.jdbc.maxIdle=5
+sonar.jdbc.minIdle=2
+sonar.jdbc.maxWait=5000
+sonar.jdbc.minEvictableIdleTimeMillis=600000
+sonar.jdbc.timeBetweenEvictionRunsMillis=30000
+
+
+
+#--------------------------------------------------------------------------------------------------
+# WEB SERVER
+
+# Binding IP address. For servers with more than one IP address, this property specifies which
+# address will be used for listening on the specified ports.
+# By default, ports will be used on all IP addresses associated with the server.
+#sonar.web.host=0.0.0.0
+
+# Web context. When set, it must start with forward slash (for example /sonarqube).
+# The default value is root context (empty value).
+#sonar.web.context=
+
+# TCP port for incoming HTTP connections. Disabled when value is -1.
+#sonar.web.port=9000
+
+# TCP port for incoming HTTPS connections. Disabled when value is -1 (default).
+#sonar.web.https.port=-1
+
+# HTTPS - the alias used to for the server certificate in the keystore.
+# If not specified the first key read in the keystore is used.
+#sonar.web.https.keyAlias=
+
+# HTTPS - the password used to access the server certificate from the
+# specified keystore file. The default value is "changeit".
+#sonar.web.https.keyPass=changeit
+
+# HTTPS - the pathname of the keystore file where is stored the server certificate.
+# By default, the pathname is the file ".keystore" in the user home.
+# If keystoreType doesn't need a file use empty value.
+#sonar.web.https.keystoreFile=
+
+# HTTPS - the password used to access the specified keystore file. The default
+# value is the value of sonar.web.https.keyPass.
+#sonar.web.https.keystorePass=
+
+# HTTPS - the type of keystore file to be used for the server certificate.
+# The default value is JKS (Java KeyStore).
+#sonar.web.https.keystoreType=JKS
+
+# HTTPS - the name of the keystore provider to be used for the server certificate.
+# If not specified, the list of registered providers is traversed in preference order
+# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType).
+#sonar.web.https.keystoreProvider=
+
+# HTTPS - the pathname of the truststore file which contains trusted certificate authorities.
+# By default, this would be the cacerts file in your JRE.
+# If truststoreFile doesn't need a file use empty value.
+#sonar.web.https.truststoreFile=
+
+# HTTPS - the password used to access the specified truststore file.
+#sonar.web.https.truststorePass=
+
+# HTTPS - the type of truststore file to be used.
+# The default value is JKS (Java KeyStore).
+#sonar.web.https.truststoreType=JKS
+
+# HTTPS - the name of the truststore provider to be used for the server certificate.
+# If not specified, the list of registered providers is traversed in preference order
+# and the first provider that supports the truststore type is used (see sonar.web.https.truststoreType).
+#sonar.web.https.truststoreProvider=
+
+# HTTPS - whether to enable client certificate authentication.
+# The default is false (client certificates disabled).
+# Other possible values are 'want' (certificates will be requested, but not required),
+# and 'true' (certificates are required).
+#sonar.web.https.clientAuth=false
+
+# The maximum number of connections that the server will accept and process at any given time.
+# When this number has been reached, the server will not accept any more connections until
+# the number of connections falls below this value. The operating system may still accept connections
+# based on the sonar.web.connections.acceptCount property. The default value is 50 for each
+# enabled connector.
+#sonar.web.http.maxThreads=50
+#sonar.web.https.maxThreads=50
+
+# The minimum number of threads always kept running. The default value is 5 for each
+# enabled connector.
+#sonar.web.http.minThreads=5
+#sonar.web.https.minThreads=5
+
+# The maximum queue length for incoming connection requests when all possible request processing
+# threads are in use. Any requests received when the queue is full will be refused.
+# The default value is 25 for each enabled connector.
+#sonar.web.http.acceptCount=25
+#sonar.web.https.acceptCount=25
+
+# Access logs are generated in the file logs/access.log. This file is rolled over when it's 5Mb.
+# An archive of 3 files is kept in the same directory.
+# Access logs are enabled by default.
+#sonar.web.accessLogs.enable=true
+
+# TCP port for incoming AJP connections. Disabled when value is -1.
+# sonar.ajp.port=9009
+
+
+
+#--------------------------------------------------------------------------------------------------
+# UPDATE CENTER
+
+# The Update Center requires an internet connection to request http://update.sonarsource.org
+# It is enabled by default.
+#sonar.updatecenter.activate=true
+
+# HTTP proxy (default none)
+#http.proxyHost=
+#http.proxyPort=
+
+# NT domain name if NTLM proxy is used
+#http.auth.ntlm.domain=
+
+# SOCKS proxy (default none)
+#socksProxyHost=
+#socksProxyPort=
+
+# proxy authentication. The 2 following properties are used for HTTP and SOCKS proxies.
+#http.proxyUser=
+#http.proxyPassword=
+
+
+#--------------------------------------------------------------------------------------------------
+# NOTIFICATIONS
+
+# Delay in seconds between processing of notification queue. Default is 60.
+#sonar.notifications.delay=60
+
+
+#--------------------------------------------------------------------------------------------------
+# PROFILING
+# Level of information displayed in the logs: NONE (default), BASIC (functional information) and FULL (functional and technical details)
+#sonar.log.profilingLevel=NONE
+
+
+#--------------------------------------------------------------------------------------------------
+# DEVELOPMENT MODE
+# Only for debugging
+
+# Set to true to apply Ruby on Rails code changes on the fly
+#sonar.rails.dev=false
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties b/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties
new file mode 100644
index 00000000000..5c06e58a32e
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties
@@ -0,0 +1,3 @@
+hello: world
+foo=bar
+java.io.tmpdir=/should/be/overridden
diff --git a/server/sonar-process/src/test/resources/sonar-dummy-app.jar b/server/sonar-process/src/test/resources/sonar-dummy-app.jar
new file mode 100644
index 00000000000..6dfd458329a
--- /dev/null
+++ b/server/sonar-process/src/test/resources/sonar-dummy-app.jar
Binary files differ