aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-process
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2014-07-10 14:14:32 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2014-07-10 14:16:46 +0200
commit4659898b6d290ce2f5b3edf7396e32e4caa2bb24 (patch)
tree8968ca6d59ebfb85a220617d52f00d1ec05d2927 /server/sonar-process
parentf09ad0c18ab1e326722e0fb224c05dc62b0e7c0e (diff)
downloadsonarqube-4659898b6d290ce2f5b3edf7396e32e4caa2bb24.tar.gz
sonarqube-4659898b6d290ce2f5b3edf7396e32e4caa2bb24.zip
Move sonar-server, sonar-search and sonar-process into sub-dir server/
Diffstat (limited to 'server/sonar-process')
-rw-r--r--server/sonar-process/pom.xml71
-rw-r--r--server/sonar-process/src/main/java/org/sonar/Application.java69
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/AesCipher.java137
-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.java49
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Encryption.java66
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Launcher.java90
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Process.java114
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Props.java101
-rw-r--r--server/sonar-process/src/test/java/org/sonar/ApplicationTest.java28
-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/ConfigurationUtilsTest.java55
-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/LauncherTest.java24
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java112
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/PropsTest.java96
-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.properties211
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/PropsTest/sonar.properties3
24 files changed, 1539 insertions, 0 deletions
diff --git a/server/sonar-process/pom.xml b/server/sonar-process/pom.xml
new file mode 100644
index 00000000000..70a992f1ac1
--- /dev/null
+++ b/server/sonar-process/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-server-parent</artifactId>
+ <version>4.5-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>sonar-process</artifactId>
+ <packaging>jar</packaging>
+ <name>SonarQube :: Process</name>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easytesting</groupId>
+ <artifactId>fest-assert</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/server/sonar-process/src/main/java/org/sonar/Application.java b/server/sonar-process/src/main/java/org/sonar/Application.java
new file mode 100644
index 00000000000..829f264940a
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/Application.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Launcher;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @Since 4.5
+ */
+public class Application {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(Application.class);
+
+
+ private static DatagramSocket systemAvailableSocket() throws IOException {
+ return new DatagramSocket(0);
+ }
+
+ public static void main(String... args) throws InterruptedException, IOException {
+
+ final ExecutorService executor = Executors.newFixedThreadPool(2);
+ final Launcher sonarQube = new Launcher("SQ", systemAvailableSocket());
+ final Launcher elasticsearch = new Launcher("ES", systemAvailableSocket());
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ LOGGER.info("Shutting down sonar Node");
+ sonarQube.interrupt();
+ elasticsearch.interrupt();
+ executor.shutdownNow();
+ }
+ });
+
+ LOGGER.info("Starting SQ Node...");
+ executor.submit(sonarQube);
+
+ LOGGER.info("Starting ES Node...");
+ executor.submit(elasticsearch);
+
+ while (!executor.isTerminated()) {
+ Thread.sleep(1000);
+ }
+ }
+}
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..b17de206349
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/AesCipher.java
@@ -0,0 +1,137 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+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 extends 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
+ 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 (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ 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 (Exception e) {
+ throw Throwables.propagate(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);
+ }
+
+ @VisibleForTesting
+ Key loadSecretFileFromFile(@Nullable 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);
+ }
+ }
+
+ @VisibleForTesting
+ String getPathToSecretKey() {
+ if (StringUtils.isBlank(pathToSecretKey)) {
+ pathToSecretKey = new File(FileUtils.getUserDirectoryPath(), ".sonar/sonar-secret.txt").getPath();
+ }
+ return pathToSecretKey;
+ }
+
+ public void setPathToSecretKey(@Nullable String pathToSecretKey) {
+ this.pathToSecretKey = 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..0ad817e7e9c
--- /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 extends Cipher {
+ @Override
+ String encrypt(String clearText) {
+ return new String(Base64.encodeBase64(clearText.getBytes()));
+ }
+
+ @Override
+ 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..4d24cf2df56
--- /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;
+
+abstract class Cipher {
+ abstract String encrypt(String clearText);
+
+ abstract 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..ba2c1f29f47
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/ConfigurationUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.text.StrSubstitutor;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+
+public final class ConfigurationUtils {
+
+ private ConfigurationUtils() {
+ // Utility class
+ }
+
+ static Properties interpolateEnvVariables(Properties properties) {
+ return interpolateVariables(properties, System.getenv());
+ }
+
+ 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;
+ }
+}
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..0de37cc4456
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Encryption.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ImmutableMap;
+
+import javax.annotation.Nullable;
+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;
+ private static final Pattern ENCRYPTED_PATTERN = Pattern.compile("\\{(.*?)\\}(.*)");
+
+ public Encryption(@Nullable String pathToSecretKey) {
+ aesCipher = new AesCipher(pathToSecretKey);
+ ciphers = ImmutableMap.of(
+ BASE64_ALGORITHM, new Base64Cipher(),
+ 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/Launcher.java b/server/sonar-process/src/main/java/org/sonar/process/Launcher.java
new file mode 100644
index 00000000000..fd354c48590
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Launcher.java
@@ -0,0 +1,90 @@
+/*
+ * 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.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+/**
+ * @Since 4.5
+ */
+public class Launcher extends Thread {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(Launcher.class);
+
+ final String name;
+ final DatagramSocket socket;
+ java.lang.Process process;
+
+
+ public Launcher(String name, DatagramSocket socket) {
+ LOGGER.info("Creating Launcher for '{}' with base port: {}", name, socket.getLocalPort());
+ this.name = name;
+ this.socket = socket;
+ }
+
+ private void launch() {
+// new Thread(new Runnable() {
+// @Override
+// public void run() {
+// Runner.main(name, socket.getLocalPort() + "");
+// }
+// }).start();
+ }
+
+ private void shutdown() {
+ process.destroy();
+ }
+
+ private void monitor() {
+ long ping = Long.MAX_VALUE;
+ while (true) {
+ LOGGER.info("My heart is beating");
+ DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
+ try {
+ socket.setSoTimeout(3000);
+ socket.receive(packet);
+ } catch (Exception e) {
+ // Do nothing
+ }
+ long newPing = System.currentTimeMillis();
+ String message = new String(packet.getData(), 0, 0, packet.getLength());
+ LOGGER.info("{} last seen since {}ms", message, (newPing - ping));
+ if ((newPing - ping) > 3000) {
+ // close everything here...
+ }
+ ping = newPing;
+ }
+ }
+
+ @Override
+ public void run() {
+ LOGGER.info("launching child VM for " + name);
+ launch();
+
+ LOGGER.info("Monitoring VM for " + name);
+ while (true) {
+ monitor();
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/Process.java b/server/sonar-process/src/main/java/org/sonar/process/Process.java
new file mode 100644
index 00000000000..b0adaf64431
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Process.java
@@ -0,0 +1,114 @@
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+
+/**
+ * @Since 4.5
+ */
+public abstract class Process implements Runnable {
+
+ public static final String NAME_PROPERTY = "pName";
+ public static final String HEARTBEAT_PROPERTY = "pPort";
+
+ public static final String MISSING_NAME_ARGUMENT = "Missing Name argument";
+ public static final String MISSING_PORT_ARGUMENT = "Missing Port argument";
+
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(Process.class);
+
+ protected Long heartBeatInterval = 1000L;
+ private final Thread monitor;
+
+ final String name;
+ final Integer port;
+
+ final protected Props props;
+
+ public Process(Props props) {
+
+ // Loading all Properties from file
+ this.props = props;
+ this.name = props.of(NAME_PROPERTY, null);
+ this.port = props.intOf(HEARTBEAT_PROPERTY);
+
+
+ // Testing required properties
+ if (StringUtils.isEmpty(this.name)) {
+ throw new IllegalStateException(MISSING_NAME_ARGUMENT);
+ }
+
+ if (this.port == null) {
+ throw new IllegalStateException(MISSING_PORT_ARGUMENT);
+ }
+
+ // Adding a shutdown hook
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ shutdown();
+ }
+ });
+
+ //Starting monitoring thread
+ this.monitor = new Thread(this);
+ this.monitor.start();
+ }
+
+ public abstract void execute();
+
+ public void shutdown() {
+ this.monitor.interrupt();
+ }
+
+ @Override
+ public void run() {
+ LOGGER.info("Setting up heartbeat on port '{}'", port);
+ DatagramPacket client = null;
+ try {
+ byte[] data = new byte[name.length()];
+ name.getBytes(0, name.length(), data, 0);
+ DatagramPacket pack =
+ new DatagramPacket(data, data.length, InetAddress.getLocalHost(), port);
+ while (!Thread.currentThread().isInterrupted()) {
+ LOGGER.trace("My heart is beating");
+ DatagramSocket ds = new DatagramSocket();
+ ds.send(pack);
+ ds.close();
+ try {
+ Thread.sleep(heartBeatInterval);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Monitoring Thread for " + name + " could not communicate to socket", e);
+ }
+ System.out.println("Closing application");
+ }
+} \ No newline at end of file
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..0eb24a37ba5
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java
@@ -0,0 +1,101 @@
+/*
+ * 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.Map;
+import java.util.Properties;
+
+public class Props {
+
+ private final Properties props;
+
+ Props(Properties props) {
+ this.props = props;
+ }
+
+ public String of(String key) {
+ return props.getProperty(key);
+ }
+
+ public String of(String key, @Nullable String defaultValue) {
+ String s = of(key);
+ return s == null ? defaultValue : s;
+ }
+
+ public boolean booleanOf(String key) {
+ String s = of(key);
+ return s != null && Boolean.parseBoolean(s);
+ }
+
+ public boolean booleanOf(String key, boolean defaultValue) {
+ String s = of(key);
+ return s != null ? Boolean.parseBoolean(s) : defaultValue;
+ }
+
+ public Integer intOf(String key) {
+ String s = of(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 intOf(String key, int defaultValue) {
+ Integer i = intOf(key);
+ return i == null ? defaultValue : i;
+ }
+
+ public static Props create(Properties properties) {
+ Properties p = new Properties();
+
+ // order is important : the last override the first
+ p.putAll(System.getenv());
+ p.putAll(System.getProperties());
+ p.putAll(properties);
+
+ p = ConfigurationUtils.interpolateEnvVariables(p);
+ p = decrypt(p);
+
+ // Set all properties as system properties to pass them to PlatformServletContextListener
+ // System.setProperties(p);
+
+ return new Props(p);
+ }
+
+ static Properties decrypt(Properties properties) {
+ Encryption encryption = new Encryption(properties.getProperty(AesCipher.ENCRYPTION_SECRET_KEY_PATH));
+ Properties result = new Properties();
+
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+ String key = (String) entry.getKey();
+ String value = (String) entry.getValue();
+ if (encryption.isEncrypted(value)) {
+ value = encryption.decrypt(value);
+ }
+ result.setProperty(key, value);
+ }
+ return result;
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/ApplicationTest.java b/server/sonar-process/src/test/java/org/sonar/ApplicationTest.java
new file mode 100644
index 00000000000..66c86ec950f
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/ApplicationTest.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;
+
+
+public class ApplicationTest {
+
+ public void dummy() throws InterruptedException {
+
+ }
+} \ No newline at end of file
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/ConfigurationUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java
new file mode 100644
index 00000000000..6191eb2ba1f
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/ConfigurationUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.junit.Test;
+
+import java.util.Map;
+import java.util.Properties;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ConfigurationUtilsTest {
+ @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.size(), is(3));
+ assertThat(output.getProperty("hello"), is("world"));
+ assertThat(output.getProperty("url"), is("jdbc:h2:mem"));
+ assertThat(output.getProperty("do_not_change"), is("${SONAR_JDBC_URL}"));
+
+ // input is not changed
+ assertThat(input.size(), is(3));
+ assertThat(input.getProperty("hello"), is("world"));
+ assertThat(input.getProperty("url"), is("${env:SONAR_JDBC_URL}"));
+ assertThat(input.getProperty("do_not_change"), is("${SONAR_JDBC_URL}"));
+ }
+
+}
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/LauncherTest.java b/server/sonar-process/src/test/java/org/sonar/process/LauncherTest.java
new file mode 100644
index 00000000000..22bdd9683a6
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/LauncherTest.java
@@ -0,0 +1,24 @@
+/*
+ * 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 LauncherTest {
+
+} \ No newline at end of file
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java
new file mode 100644
index 00000000000..e9356437ae9
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.MalformedURLException;
+import java.net.SocketException;
+import java.net.URISyntaxException;
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ProcessTest {
+
+ DatagramSocket socket;
+
+ @Before
+ public void setup() throws SocketException {
+ this.socket = new DatagramSocket(0);
+ }
+
+ @After
+ public void tearDown() {
+ this.socket.close();
+ }
+
+ @Test
+ public void fail_missing_properties() throws MalformedURLException, URISyntaxException {
+ Properties properties = new Properties();
+ try {
+ testProcess(properties);
+ } catch (Exception e) {
+ assertThat(e.getMessage()).isEqualTo(Process.MISSING_NAME_ARGUMENT);
+ }
+
+ properties.setProperty(Process.NAME_PROPERTY, "test");
+ try {
+ testProcess(properties);
+ } catch (Exception e) {
+ assertThat(e.getMessage()).isEqualTo(Process.MISSING_PORT_ARGUMENT);
+ }
+
+ properties.setProperty(Process.HEARTBEAT_PROPERTY, Integer.toString(socket.getLocalPort()));
+ assertThat(testProcess(properties)).isNotNull();
+ }
+
+ @Test
+ public void heart_beats() {
+ Properties properties = new Properties();
+ properties.setProperty(Process.NAME_PROPERTY, "test");
+ properties.setProperty(Process.HEARTBEAT_PROPERTY, Integer.toString(socket.getLocalPort()));
+ Process test = testProcess(properties);
+
+ assertThat(test).isNotNull();
+
+ int ping = 0;
+ int loop = 0;
+ while (loop < 3) {
+ DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
+ try {
+ socket.setSoTimeout(2000);
+ socket.receive(packet);
+ ping++;
+ } catch (Exception e) {
+ // Do nothing
+ }
+ loop++;
+ }
+
+ assertThat(ping).isEqualTo(loop);
+ }
+
+ private Process testProcess(Properties properties) {
+ return new Process(Props.create(properties)) {
+ @Override
+ public void execute() {
+ try {
+ Thread.sleep(10000L);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+ };
+ }
+} \ No newline at end of file
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..775d24a2a64
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/PropsTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.of("foo")).isEqualTo("bar");
+ assertThat(props.of("foo", "default value")).isEqualTo("bar");
+ assertThat(props.of("unknown")).isNull();
+ assertThat(props.of("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.intOf("foo")).isEqualTo(33);
+ assertThat(props.intOf("foo", 44)).isEqualTo(33);
+ assertThat(props.intOf("blank")).isNull();
+ assertThat(props.intOf("blank", 55)).isEqualTo(55);
+ assertThat(props.intOf("unknown")).isNull();
+ assertThat(props.intOf("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.intOf("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.booleanOf("foo")).isTrue();
+ assertThat(props.booleanOf("bar")).isFalse();
+ assertThat(props.booleanOf("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.booleanOf("unset", false)).isFalse();
+ assertThat(props.booleanOf("unset", true)).isTrue();
+ assertThat(props.booleanOf("foo", false)).isTrue();
+ assertThat(props.booleanOf("bar", true)).isFalse();
+ }
+}
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..6c058aa3227
--- /dev/null
+++ b/server/sonar-process/src/test/resources/org/sonar/process/ProcessTest/sonar.properties
@@ -0,0 +1,211 @@
+# 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
+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