aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2014-07-22 00:02:51 +0200
committerStephane Gamard <stephane.gamard@searchbox.com>2014-07-23 15:34:03 +0200
commit313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f (patch)
tree6fc817743c48da2d944b8b04cb75ada6207bba87
parentd0f9dca7b2d3dd27ab0b43ecdd9c0ea93d4ae734 (diff)
downloadsonarqube-313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f.tar.gz
sonarqube-313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f.zip
Experimental fork
-rw-r--r--server/pom.xml1
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java19
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Props.java4
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java26
-rw-r--r--server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java34
-rw-r--r--server/sonar-server-app/pom.xml61
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/Connectors.java167
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/EmbeddedTomcat.java130
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/Env.java69
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/Logging.java81
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/NullJarScanner.java36
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/ServerProcess.java52
-rw-r--r--server/sonar-server-app/src/main/java/org/sonar/server/app/Webapp.java77
-rw-r--r--server/sonar-server/pom.xml27
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java31
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java2
-rw-r--r--server/sonar-web/pom.xml13
-rw-r--r--sonar-application/assembly.xml12
-rw-r--r--sonar-application/pom.xml14
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties4
-rw-r--r--sonar-application/src/main/assembly/conf/wrapper.conf8
-rw-r--r--sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java8
-rw-r--r--sonar-application/src/main/java/org/sonar/application/ForkProcesses.java104
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Installation.java161
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Webapp.java1
-rw-r--r--sonar-application/src/main/resources/logback.xml12
30 files changed, 1043 insertions, 119 deletions
diff --git a/server/pom.xml b/server/pom.xml
index 131d3efcbcd..b26febac729 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -14,6 +14,7 @@
<module>sonar-process</module>
<module>sonar-search</module>
<module>sonar-server</module>
+ <module>sonar-server-app</module>
<module>sonar-web</module>
<module>sonar-ws-client</module>
</modules>
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java
index 2ec28711b70..0f0f8ff2fd0 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java
@@ -103,8 +103,8 @@ public class ProcessWrapper extends Thread {
LOGGER.info("Try #{} to connect to JMX server for process '{}'", i, name);
try {
String protocol = "rmi";
- String path = "/jndi/rmi://" + InetAddress.getLocalHost().getHostName() + ":" + port + "/jmxrmi";
- JMXServiceURL jmxUrl = new JMXServiceURL(protocol, InetAddress.getLocalHost().getHostName(), port, path);
+ String path = "/jndi/rmi://" + InetAddress.getLocalHost().getHostAddress() + ":" + port + "/jmxrmi";
+ JMXServiceURL jmxUrl = new JMXServiceURL(protocol, InetAddress.getLocalHost().getHostAddress(), port, path);
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null);
MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection();
ProcessMXBean bean = JMX.newMBeanProxy(mBeanServer, Process.objectNameFor(name), ProcessMXBean.class);
@@ -147,7 +147,7 @@ public class ProcessWrapper extends Thread {
}
private List<String> getJMXOptions() {
- return ImmutableList.<String>of(
+ return ImmutableList.of(
"-Dcom.sun.management.jmxremote",
"-Dcom.sun.management.jmxremote.port=" + this.port,
"-Dcom.sun.management.jmxremote.authenticate=false",
@@ -155,18 +155,13 @@ public class ProcessWrapper extends Thread {
}
private List<String> getClassPath() {
- String separator = System.getProperty("file.separator");
- return ImmutableList.<String>of(
- "-cp",
- StringUtils.join(classPath, separator));
+ // java specification : "multiple path entries are separated by semi-colons", not by
+ // system file separator,
+ return ImmutableList.of("-cp", StringUtils.join(classPath, ";"));
}
private String getPropertyFile() {
File propertyFile = new File(FileUtils.getTempDirectory(), UUID.randomUUID().toString());
-// if (!propertyFile.canWrite()) {
-// throw new IllegalStateException("Cannot write temp propertyFile to '" +
-// propertyFile.getAbsolutePath() + "'");
-// }
try {
Properties props = new Properties();
for (Map.Entry<String, String> property : properties.entrySet()) {
@@ -189,6 +184,7 @@ public class ProcessWrapper extends Thread {
LOGGER.info("ProcessWrapper::executeProcess() START");
ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.environment().put("SONAR_HOME", workDir);
processBuilder.command().add(getJavaCommand());
if (!StringUtils.isEmpty(javaOpts)) {
@@ -199,6 +195,7 @@ public class ProcessWrapper extends Thread {
}
processBuilder.command().addAll(getJMXOptions());
processBuilder.command().addAll(getClassPath());
+
processBuilder.command().add(className);
processBuilder.command().add(getPropertyFile());
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
index 0eb24a37ba5..cd752403a86 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/Props.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java
@@ -67,6 +67,10 @@ public class Props {
return i == null ? defaultValue : i;
}
+ public Properties properties() {
+ return props;
+ }
+
public static Props create(Properties properties) {
Properties p = new Properties();
diff --git a/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java b/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java
index 5c44678b51c..a2e3bf42680 100644
--- a/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java
+++ b/server/sonar-search/src/main/java/org/sonar/search/ElasticSearch.java
@@ -25,23 +25,18 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
-import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.process.Process;
import org.sonar.process.Props;
import org.sonar.search.script.ListUpdate;
-public class ElasticSearch extends Process {
+import java.io.File;
- private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearch.class);
+public class ElasticSearch extends Process {
public static final String ES_DEBUG_PROPERTY = "esDebug";
- public static final String ES_PORT_PROPERTY = "esPort";
+ public static final String ES_PORT_PROPERTY = "sonar.es.node.port";
public static final String ES_CLUSTER_PROPERTY = "esCluster";
- public static final String ES_HOME_PROPERTY = "esHome";
-
- public static final String MISSING_ES_PORT = "Missing ES port Argument";
- public static final String MISSING_ES_HOME = "Missing ES home directory Argument";
public static final String DEFAULT_CLUSTER_NAME = "sonarqube";
@@ -72,22 +67,13 @@ public class ElasticSearch extends Process {
@Override
public void onStart() {
- String home = props.of(ES_HOME_PROPERTY);
- if (home == null) {
- throw new IllegalStateException(MISSING_ES_HOME);
- }
-
+ String dataDir = props.of("sonar.path.data");
Integer port = props.intOf(ES_PORT_PROPERTY);
- if (port == null) {
- throw new IllegalStateException(MISSING_ES_PORT);
- }
-
String clusterName = props.of(ES_CLUSTER_PROPERTY, DEFAULT_CLUSTER_NAME);
- LOGGER.info("Starting ES[{}] on port: {}", clusterName, port);
+ LoggerFactory.getLogger(ElasticSearch.class).info("Starting ES[{}] on port: {}", clusterName, port);
ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder()
-
.put("es.foreground", "yes")
.put("discovery.zen.ping.multicast.enabled", "false")
@@ -108,7 +94,7 @@ public class ElasticSearch extends Process {
.put("node.data", true)
.put("node.local", false)
.put("transport.tcp.port", port)
- .put("path.home", home);
+ .put("path.data", new File(dataDir, "es").getAbsolutePath());
if (props.booleanOf(ES_DEBUG_PROPERTY, false)) {
esSettings
diff --git a/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java b/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java
index 743b2bc427b..d06de0fac40 100644
--- a/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java
+++ b/server/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java
@@ -79,42 +79,12 @@ public class ElasticSearchTest {
}
}
-
- @Test
- public void missing_properties() throws IOException, MBeanRegistrationException, InstanceNotFoundException {
-
- Properties properties = new Properties();
- properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath());
- properties.setProperty(Process.NAME_PROPERTY, "ES");
- properties.setProperty(Process.PORT_PROPERTY, Integer.toString(freePort));
-
- try {
- elasticSearch = new ElasticSearch(Props.create(properties));
- } catch (Exception e) {
- assertThat(e.getMessage()).isEqualTo(ElasticSearch.MISSING_ES_HOME);
- }
-
- properties.setProperty(ElasticSearch.ES_HOME_PROPERTY, tempDirectory.getAbsolutePath());
- try {
- resetMBeanServer();
- elasticSearch = new ElasticSearch(Props.create(properties));
- } catch (Exception e) {
- assertThat(e.getMessage()).isEqualTo(ElasticSearch.MISSING_ES_PORT);
- }
- resetMBeanServer();
-
- properties.setProperty(ElasticSearch.ES_PORT_PROPERTY, Integer.toString(freeESPort));
- elasticSearch = new ElasticSearch(Props.create(properties));
- assertThat(elasticSearch).isNotNull();
- }
-
@Test
public void can_connect() throws SocketException {
-
Properties properties = new Properties();
properties.setProperty(Process.SONAR_HOME, FileUtils.getTempDirectoryPath());
properties.setProperty(Process.NAME_PROPERTY, "ES");
- properties.setProperty(ElasticSearch.ES_HOME_PROPERTY, tempDirectory.getAbsolutePath());
+ properties.setProperty("sonar.path.data", tempDirectory.getAbsolutePath());
properties.setProperty(ElasticSearch.ES_PORT_PROPERTY, Integer.toString(freeESPort));
elasticSearch = new ElasticSearch(Props.create(properties));
@@ -157,4 +127,4 @@ public class ElasticSearchTest {
assertThat(e.getMessage()).isEqualTo("No node available");
}
}
-} \ No newline at end of file
+}
diff --git a/server/sonar-server-app/pom.xml b/server/sonar-server-app/pom.xml
new file mode 100644
index 00000000000..d21903df63f
--- /dev/null
+++ b/server/sonar-server-app/pom.xml
@@ -0,0 +1,61 @@
+<?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">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>server</artifactId>
+ <version>4.5-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>sonar-server-app</artifactId>
+ <packaging>jar</packaging>
+ <name>SonarQube :: Server :: App</name>
+ <description>Temporary bootstrapper of SonarQube web server</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-process</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-access</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat.embed</groupId>
+ <artifactId>tomcat-embed-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat.embed</groupId>
+ <artifactId>tomcat-embed-jasper</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat.embed</groupId>
+ <artifactId>tomcat-embed-logging-juli</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/Connectors.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/Connectors.java
new file mode 100644
index 00000000000..fa14a25ceaf
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/Connectors.java
@@ -0,0 +1,167 @@
+/*
+ * 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.server.app;
+
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Props;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class Connectors {
+
+ private static final int DISABLED_PORT = -1;
+ static final String HTTP_PROTOCOL = "HTTP/1.1";
+ static final String AJP_PROTOCOL = "AJP/1.3";
+
+ static void configure(Tomcat tomcat, Props props) {
+ configureShutdown(tomcat, props);
+ configureConnectors(tomcat, props);
+ }
+
+ private static void configureConnectors(Tomcat tomcat, Props props) {
+ List<Connector> connectors = new ArrayList<Connector>();
+ connectors.addAll(Arrays.asList(newHttpConnector(props), newAjpConnector(props), newHttpsConnector(props)));
+ connectors.removeAll(Collections.singleton(null));
+
+ verify(connectors);
+
+ tomcat.setConnector(connectors.get(0));
+ for (Connector connector : connectors) {
+ tomcat.getService().addConnector(connector);
+ }
+ }
+
+ private static void verify(List<Connector> connectors) {
+ if (connectors.isEmpty()) {
+ throw new IllegalStateException("HTTP connectors are disabled");
+ }
+ Set<Integer> ports = new HashSet<Integer>();
+ for (Connector connector : connectors) {
+ int port = connector.getPort();
+ if (ports.contains(port)) {
+ throw new IllegalStateException(String.format("HTTP, AJP and HTTPS must not use the same port %d", port));
+ }
+ ports.add(port);
+ }
+ }
+
+ private static void configureShutdown(Tomcat tomcat, Props props) {
+ String shutdownToken = props.of("sonar.web.shutdown.token");
+ Integer shutdownPort = props.intOf("sonar.web.shutdown.port");
+ if (shutdownToken != null && !"".equals(shutdownToken) && shutdownPort != null) {
+ tomcat.getServer().setPort(shutdownPort);
+ tomcat.getServer().setShutdown(shutdownToken);
+ info("Shutdown command is enabled on port " + shutdownPort);
+ }
+ }
+
+ @Nullable
+ private static Connector newHttpConnector(Props props) {
+ Connector connector = null;
+ // Not named "sonar.web.http.port" to keep backward-compatibility
+ int port = props.intOf("sonar.web.port", 9000);
+ if (port > DISABLED_PORT) {
+ connector = newConnector(props, HTTP_PROTOCOL, "http");
+ connector.setPort(port);
+ info("HTTP connector is enabled on port " + port);
+ }
+ return connector;
+ }
+
+ @Nullable
+ private static Connector newAjpConnector(Props props) {
+ Connector connector = null;
+ int port = props.intOf("sonar.ajp.port", DISABLED_PORT);
+ if (port > DISABLED_PORT) {
+ connector = newConnector(props, AJP_PROTOCOL, "http");
+ connector.setPort(port);
+ info("AJP connector is enabled on port " + port);
+ }
+ return connector;
+ }
+
+ @Nullable
+ private static Connector newHttpsConnector(Props props) {
+ Connector connector = null;
+ int port = props.intOf("sonar.web.https.port", DISABLED_PORT);
+ if (port > DISABLED_PORT) {
+ connector = newConnector(props, HTTP_PROTOCOL, "https");
+ connector.setPort(port);
+ connector.setSecure(true);
+ connector.setScheme("https");
+ setConnectorAttribute(connector, "keyAlias", props.of("sonar.web.https.keyAlias"));
+ String keyPassword = props.of("sonar.web.https.keyPass", "changeit");
+ setConnectorAttribute(connector, "keyPass", keyPassword);
+ setConnectorAttribute(connector, "keystorePass", props.of("sonar.web.https.keystorePass", keyPassword));
+ setConnectorAttribute(connector, "keystoreFile", props.of("sonar.web.https.keystoreFile"));
+ setConnectorAttribute(connector, "keystoreType", props.of("sonar.web.https.keystoreType", "JKS"));
+ setConnectorAttribute(connector, "keystoreProvider", props.of("sonar.web.https.keystoreProvider"));
+ setConnectorAttribute(connector, "truststorePass", props.of("sonar.web.https.truststorePass", "changeit"));
+ setConnectorAttribute(connector, "truststoreFile", props.of("sonar.web.https.truststoreFile"));
+ setConnectorAttribute(connector, "truststoreType", props.of("sonar.web.https.truststoreType", "JKS"));
+ setConnectorAttribute(connector, "truststoreProvider", props.of("sonar.web.https.truststoreProvider"));
+ setConnectorAttribute(connector, "clientAuth", props.of("sonar.web.https.clientAuth", "false"));
+ setConnectorAttribute(connector, "sslProtocol", "TLS");
+ setConnectorAttribute(connector, "SSLEnabled", true);
+ info("HTTPS connector is enabled on port " + port);
+ }
+ return connector;
+ }
+
+ private static Connector newConnector(Props props, String protocol, String scheme) {
+ Connector connector = new Connector(protocol);
+ connector.setURIEncoding("UTF-8");
+ connector.setProperty("address", props.of("sonar.web.host", "0.0.0.0"));
+ configurePool(props, connector, scheme);
+ configureCompression(connector);
+ return connector;
+ }
+
+ private static void configurePool(Props props, Connector connector, String scheme) {
+ connector.setProperty("acceptorThreadCount", String.valueOf(2));
+ connector.setProperty("minSpareThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".minThreads", 5)));
+ connector.setProperty("maxThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".maxThreads", 50)));
+ connector.setProperty("acceptCount", String.valueOf(props.intOf("sonar.web." + scheme + ".acceptCount", 25)));
+ }
+
+ private static void configureCompression(Connector connector) {
+ connector.setProperty("compression", "on");
+ connector.setProperty("compressionMinSize", "1024");
+ connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,application/json,application/javascript");
+ }
+
+ private static void setConnectorAttribute(Connector c, String key, @Nullable Object value) {
+ if (value != null) {
+ c.setAttribute(key, value);
+ }
+ }
+
+ private static void info(String message) {
+ LoggerFactory.getLogger(Connectors.class).info(message);
+ }
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
new file mode 100644
index 00000000000..971414b10dd
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.app;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+
+class EmbeddedTomcat {
+
+ public static final String TEMP_RELATIVE_PATH = "temp/tomcat";
+
+ private final Env env;
+ private Tomcat tomcat = null;
+ private Thread hook = null;
+ private boolean stopping = false, ready = false;
+
+ EmbeddedTomcat(Env env) {
+ this.env = env;
+ }
+
+ void start() {
+ if (tomcat != null || hook != null) {
+ throw new IllegalStateException("Server is already started");
+ }
+
+ try {
+ // '%2F' (slash /) and '%5C' (backslash \) are permitted as path delimiters in URLs
+ // See Ruby on Rails url_for
+ System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
+
+ System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true");
+
+ tomcat = new Tomcat();
+
+ // Initialize directories
+ String basedir = env.freshDir(TEMP_RELATIVE_PATH).getCanonicalPath();
+ tomcat.setBaseDir(basedir);
+ tomcat.getHost().setAppBase(basedir);
+ tomcat.getHost().setAutoDeploy(false);
+ tomcat.getHost().setCreateDirs(false);
+ tomcat.getHost().setDeployOnStartup(true);
+
+ Logging.configure(tomcat, env, env.props());
+ Connectors.configure(tomcat, env.props());
+ Webapp.configure(tomcat, env, env.props());
+ tomcat.start();
+ addShutdownHook();
+ ready = true;
+ tomcat.getServer().await();
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to start web server", e);
+ }
+ // Shutdown command received
+ stop();
+ }
+
+ private void addShutdownHook() {
+ hook = new Thread() {
+ @Override
+ public void run() {
+ EmbeddedTomcat.this.doStop();
+ }
+ };
+ Runtime.getRuntime().addShutdownHook(hook);
+ }
+
+
+ void stop() {
+ removeShutdownHook();
+ doStop();
+ }
+
+ private synchronized void doStop() {
+ try {
+ if (tomcat != null && !stopping) {
+ stopping = true;
+ tomcat.stop();
+ tomcat.destroy();
+ }
+ tomcat = null;
+ stopping = false;
+ ready = false;
+ File tempDir = env.file(TEMP_RELATIVE_PATH);
+ FileUtils.deleteQuietly(tempDir);
+
+ } catch (LifecycleException e) {
+ throw new IllegalStateException("Fail to stop web server", e);
+ }
+ }
+
+ private void removeShutdownHook() {
+ if (hook != null && !hook.isAlive()) {
+ Runtime.getRuntime().removeShutdownHook(hook);
+ hook = null;
+ }
+ }
+
+ boolean isReady( ){
+ return ready;
+ }
+
+ int port() {
+ Connector[] connectors = tomcat.getService().findConnectors();
+ if (connectors.length > 0) {
+ return connectors[0].getLocalPort();
+ }
+ return -1;
+ }
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/Env.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/Env.java
new file mode 100644
index 00000000000..885fbbad7b1
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/Env.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.server.app;
+
+import org.apache.commons.io.FileUtils;
+import org.sonar.process.Props;
+
+import java.io.File;
+import java.io.IOException;
+
+class Env {
+
+ private final Props props;
+
+ Env(Props props) {
+ this.props = props;
+ }
+
+ Props props() {
+ return props;
+ }
+
+ File rootDir() {
+ return new File(props.of("sonar.path.home"));
+ }
+
+ File file(String relativePath) {
+ return new File(rootDir(), relativePath);
+ }
+
+ File freshDir(String relativePath) {
+ File dir = new File(rootDir(), relativePath);
+ FileUtils.deleteQuietly(dir);
+ dir.mkdirs();
+ return dir;
+ }
+
+ /**
+ * This check is required in order to provide more meaningful message than JRuby - see SONAR-2715
+ */
+ void verifyWritableTempDir() {
+ File file = null;
+ try {
+ file = File.createTempFile("sonarqube-check", "tmp");
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to create file in temporary directory, please check existence " +
+ "and permissions of: " + FileUtils.getTempDirectory(), e);
+ } finally {
+ FileUtils.deleteQuietly(file);
+ }
+ }
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/Logging.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/Logging.java
new file mode 100644
index 00000000000..45c4afefeb7
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/Logging.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.server.app;
+
+import ch.qos.logback.access.tomcat.LogbackValve;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.startup.Tomcat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+import org.sonar.process.Props;
+
+import java.util.logging.LogManager;
+
+class Logging {
+
+ static final String ACCESS_RELATIVE_PATH = "web/WEB-INF/config/logback-access.xml";
+ static final String PROPERTY_ENABLE_ACCESS_LOGS = "sonar.web.accessLogs.enable";
+
+ static void init() {
+ // Configure java.util.logging, used by Tomcat, in order to forward to slf4j
+ LogManager.getLogManager().reset();
+ SLF4JBridgeHandler.install();
+ }
+
+ static void configure(Tomcat tomcat, Env env, Props props) {
+ tomcat.setSilent(false);
+ tomcat.getService().addLifecycleListener(new LifecycleLogger(console()));
+ configureLogbackAccess(tomcat, env, props);
+ }
+
+ static Logger console() {
+ return LoggerFactory.getLogger("console");
+ }
+
+ private static void configureLogbackAccess(Tomcat tomcat, Env env, Props props) {
+ if (props.booleanOf(PROPERTY_ENABLE_ACCESS_LOGS, true)) {
+ LogbackValve valve = new LogbackValve();
+ valve.setQuiet(true);
+ valve.setFilename(env.file(ACCESS_RELATIVE_PATH).getAbsolutePath());
+ tomcat.getHost().getPipeline().addValve(valve);
+ }
+ }
+
+ static class LifecycleLogger implements LifecycleListener {
+ private Logger logger;
+
+ LifecycleLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public void lifecycleEvent(LifecycleEvent event) {
+ if ("after_start".equals(event.getType())) {
+ logger.info("Web server is started");
+
+ } else if ("after_destroy".equals(event.getType())) {
+ logger.info("Web server is stopped");
+ }
+ }
+ }
+
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/NullJarScanner.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/NullJarScanner.java
new file mode 100644
index 00000000000..4f8ac9e5312
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/NullJarScanner.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.server.app;
+
+import org.apache.tomcat.JarScanner;
+import org.apache.tomcat.JarScannerCallback;
+
+import javax.servlet.ServletContext;
+import java.util.Set;
+
+/**
+ * Disable taglib and web-fragment.xml scanning of Tomcat. Should speed up startup.
+ */
+class NullJarScanner implements JarScanner {
+ @Override
+ public void scan(ServletContext context, ClassLoader classloader, JarScannerCallback callback, Set<String> jarsToSkip) {
+ // doing nothing is fast!
+ }
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/ServerProcess.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/ServerProcess.java
new file mode 100644
index 00000000000..287176a828d
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/ServerProcess.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.server.app;
+
+public class ServerProcess extends org.sonar.process.Process {
+
+ private final EmbeddedTomcat tomcat;
+
+ public ServerProcess(String[] args) {
+ super(args);
+ Logging.init();
+ Env env = new Env(props);
+ env.verifyWritableTempDir();
+ this.tomcat = new EmbeddedTomcat(env);
+ }
+
+ @Override
+ public void onStart() {
+ tomcat.start();
+ }
+
+ @Override
+ public void onTerminate() {
+ tomcat.stop();
+ }
+
+ @Override
+ public boolean isReady() {
+ return tomcat.isReady();
+ }
+
+ public static void main(String[] args) {
+ new ServerProcess(args).start();
+ }
+}
diff --git a/server/sonar-server-app/src/main/java/org/sonar/server/app/Webapp.java b/server/sonar-server-app/src/main/java/org/sonar/server/app/Webapp.java
new file mode 100644
index 00000000000..e20500f15bd
--- /dev/null
+++ b/server/sonar-server-app/src/main/java/org/sonar/server/app/Webapp.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.server.app;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.startup.Tomcat;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Props;
+
+import java.util.Map;
+
+class Webapp {
+
+ private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes";
+ private static final String RAILS_ENV = "rails.env";
+ private static final String PROPERTY_CONTEXT = "sonar.web.context";
+ private static final String PROPERTY_LOG_PROFILING_LEVEL = "sonar.log.profilingLevel";
+ private static final String PROPERTY_LOG_CONSOLE = "sonar.log.console";
+
+ static void configure(Tomcat tomcat, Env env, Props props) {
+ try {
+ Context context = tomcat.addWebapp(getContextPath(props), env.file("web").getAbsolutePath());
+ context.setConfigFile(env.file("web/META-INF/context.xml").toURI().toURL());
+ context.addParameter(PROPERTY_LOG_PROFILING_LEVEL, props.of(PROPERTY_LOG_PROFILING_LEVEL, "NONE"));
+ context.addParameter(PROPERTY_LOG_CONSOLE, props.of(PROPERTY_LOG_CONSOLE, "false"));
+ for (Map.Entry<Object, Object> entry : props.properties().entrySet()) {
+ String key = entry.getKey().toString();
+ if (key.startsWith("sonar.")) {
+ context.addParameter(key, entry.getValue().toString());
+ }
+ }
+ configureRailsMode(props, context);
+ context.setJarScanner(new NullJarScanner());
+
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to configure webapp", e);
+ }
+ }
+
+ static String getContextPath(Props props) {
+ String context = props.of(PROPERTY_CONTEXT, "");
+ if ("/".equals(context)) {
+ context = "";
+ } else if (!"".equals(context) && !context.startsWith("/")) {
+ throw new IllegalStateException(String.format("Value of '%s' must start with a forward slash: '%s'", PROPERTY_CONTEXT, context));
+ }
+ return context;
+ }
+
+ static void configureRailsMode(Props props, Context context) {
+ if (props.booleanOf("sonar.rails.dev")) {
+ context.addParameter(RAILS_ENV, "development");
+ context.addParameter(JRUBY_MAX_RUNTIMES, "3");
+ LoggerFactory.getLogger(Webapp.class).warn("\n\n\n------ RAILS DEVELOPMENT MODE IS ENABLED ------\n\n\n");
+ } else {
+ context.addParameter(RAILS_ENV, "production");
+ context.addParameter(JRUBY_MAX_RUNTIMES, "1");
+ }
+ }
+}
diff --git a/server/sonar-server/pom.xml b/server/sonar-server/pom.xml
index e462e45623d..1665e581eb8 100644
--- a/server/sonar-server/pom.xml
+++ b/server/sonar-server/pom.xml
@@ -151,33 +151,10 @@
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
+ <version>7.0.42</version>
+ <scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.apache.tomcat.embed</groupId>
- <artifactId>tomcat-embed-jasper</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.tomcat.embed</groupId>
- <artifactId>tomcat-embed-logging-juli</artifactId>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-access</artifactId>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jul-to-slf4j</artifactId>
- </dependency>
-
- <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java
index 2ed48e023e9..389258369af 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java
@@ -100,7 +100,7 @@ public class EmbeddedDatabase implements Startable {
}
private File getSonarHomeDataDirectory(Settings settings) {
- File sonarHome = new File(settings.getString(CoreProperties.SONAR_HOME));
+ File sonarHome = new File(settings.getString("sonar.path.home"));
if (!sonarHome.isDirectory()) {
throw new IllegalStateException("SonarQube home directory is not valid");
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
index 8095d1911b6..cceb7c4cb43 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
@@ -56,7 +56,7 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable {
public DefaultServerFileSystem(Database database, Settings settings, Server server) {
this.database = database;
this.server = server;
- this.homeDir = new File(settings.getString(CoreProperties.SONAR_HOME));
+ this.homeDir = new File(settings.getString("sonar.path.home"));
}
/**
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java
index 82e2e659596..ab87e7db4b8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java
@@ -24,10 +24,12 @@ import org.slf4j.LoggerFactory;
import org.sonar.core.config.Logback;
import org.sonar.core.profiling.Profiling;
+import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
-
+import java.util.Enumeration;
import java.util.Map;
+import java.util.Properties;
import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
@@ -50,7 +52,16 @@ public final class PlatformServletContextListener implements ServletContextListe
public void contextInitialized(ServletContextEvent event) {
try {
configureLogback(event);
- Platform.getInstance().init(System.getProperties());
+ Properties props = new Properties();
+ ServletContext context = event.getServletContext();
+ Enumeration<String> paramKeys = context.getInitParameterNames();
+ while (paramKeys.hasMoreElements()) {
+ String key = paramKeys.nextElement();
+ if (key.startsWith("sonar.")) {
+ props.put(key, context.getInitParameter(key));
+ }
+ }
+ Platform.getInstance().init(props);
Platform.getInstance().doStart();
} catch (Throwable t) {
// Tomcat 7 "limitations":
@@ -80,19 +91,19 @@ public final class PlatformServletContextListener implements ServletContextListe
*/
private void configureLogback(ServletContextEvent event) {
String configProfilingLevel = defaultIfEmpty(
- event.getServletContext().getInitParameter(Profiling.CONFIG_PROFILING_LEVEL),
- System.getProperty(Profiling.CONFIG_PROFILING_LEVEL));
+ event.getServletContext().getInitParameter(Profiling.CONFIG_PROFILING_LEVEL),
+ System.getProperty(Profiling.CONFIG_PROFILING_LEVEL));
Profiling.Level profilingLevel = Profiling.Level.fromConfigString(configProfilingLevel);
String consoleEnabled = defaultIfEmpty(defaultIfEmpty(
event.getServletContext().getInitParameter(CONFIG_LOG_CONSOLE),
System.getProperty(CONFIG_LOG_CONSOLE)),
- // Line below used in last resort
- "false");
+ // Line below used in last resort
+ "false");
Map<String, String> variables = ImmutableMap.of(
- "RAILS_LOGGER_LEVEL", profilingLevel == Profiling.Level.FULL ? "DEBUG" : "WARN",
- "LOGFILE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? LOGFILE_FULL_LOGGING_FORMAT : LOGFILE_STANDARD_LOGGING_FORMAT,
- "CONSOLE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? CONSOLE_FULL_LOGGING_FORMAT : CONSOLE_STANDARD_LOGGING_FORMAT,
- "CONSOLE_ENABLED", consoleEnabled);
+ "RAILS_LOGGER_LEVEL", profilingLevel == Profiling.Level.FULL ? "DEBUG" : "WARN",
+ "LOGFILE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? LOGFILE_FULL_LOGGING_FORMAT : LOGFILE_STANDARD_LOGGING_FORMAT,
+ "CONSOLE_LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? CONSOLE_FULL_LOGGING_FORMAT : CONSOLE_STANDARD_LOGGING_FORMAT,
+ "CONSOLE_ENABLED", consoleEnabled);
Logback.configure("/org/sonar/server/platform/logback.xml", variables);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
index bcff83ded1f..f1f4a4000f2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerImpl.java
@@ -77,7 +77,7 @@ public final class ServerImpl extends Server implements Startable {
// Remove trailing slashes
.replaceFirst("(\\/+)$", "");
- sonarHome = new File(settings.getString(CoreProperties.SONAR_HOME));
+ sonarHome = new File(settings.getString("sonar.path.home"));
if (!sonarHome.isDirectory()) {
throw new IllegalStateException("SonarQube home directory is not valid");
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 4c93d8a66cb..5e7613339d1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -369,7 +369,7 @@ public final class JRubyFacade {
}
public String getServerHome() {
- return get(Settings.class).getString(CoreProperties.SONAR_HOME);
+ return get(Settings.class).getString("sonar.path.home");
}
public ComponentContainer getContainer() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
index 8c7b6812118..b1bef3250f1 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
@@ -76,7 +76,7 @@ public class ServerTester extends ExternalResource {
Properties properties = new Properties();
properties.putAll(initialProps);
properties.setProperty(IndexProperties.TYPE, IndexProperties.ES_TYPE.MEMORY.name());
- properties.setProperty(CoreProperties.SONAR_HOME, homeDir.getAbsolutePath());
+ properties.setProperty("sonar.path.home", homeDir.getAbsolutePath());
properties.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:" + homeDir.getAbsolutePath() + "/h2");
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
String key = entry.getKey().toString();
diff --git a/server/sonar-web/pom.xml b/server/sonar-web/pom.xml
index cc6e2043a1b..6e3ed52fcf7 100644
--- a/server/sonar-web/pom.xml
+++ b/server/sonar-web/pom.xml
@@ -19,6 +19,13 @@
<sonar.exclusions>src/main/js/third-party/**/*,src/main/js/require.js,src/main/js/tests/**/*</sonar.exclusions>
</properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
<build>
<resources>
<resource>
@@ -361,12 +368,6 @@
<artifactId>h2</artifactId>
<scope>provided</scope>
</dependency>
- <dependency>
- <groupId>org.codehaus.sonar</groupId>
- <artifactId>sonar-server</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
<!-- core plugins -->
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
diff --git a/sonar-application/assembly.xml b/sonar-application/assembly.xml
index b063dc66c4b..9769089b39b 100644
--- a/sonar-application/assembly.xml
+++ b/sonar-application/assembly.xml
@@ -14,12 +14,22 @@
<exclude>mysql:mysql-connector-java</exclude>
<exclude>org.postgresql:postgresql</exclude>
<exclude>net.sourceforge.jtds:jtds</exclude>
+ <exclude>org.codehaus.sonar:sonar-search</exclude>
+ <exclude>org.codehaus.sonar:sonar-web</exclude>
<exclude>org.codehaus.sonar.plugins:*</exclude>
- <exclude>org.codehaus.sonar-plugins.*:*</exclude>
+ <exclude>org.codehaus.sonar-plugins.java:*</exclude>
<exclude>org.codehaus.sonar:sonar-batch-maven-compat</exclude>
</excludes>
</dependencySet>
<dependencySet>
+ <outputDirectory>lib/search</outputDirectory>
+ <useTransitiveDependencies>true</useTransitiveDependencies>
+ <useTransitiveFiltering>false</useTransitiveFiltering>
+ <includes>
+ <include>org.codehaus.sonar:sonar-search</include>
+ </includes>
+ </dependencySet>
+ <dependencySet>
<outputDirectory>lib/batch</outputDirectory>
<useTransitiveDependencies>false</useTransitiveDependencies>
<includes>
diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml
index aa9706ac130..465c9b5902f 100644
--- a/sonar-application/pom.xml
+++ b/sonar-application/pom.xml
@@ -77,14 +77,26 @@
<dependency>
<groupId>org.codehaus.sonar</groupId>
- <artifactId>sonar-server</artifactId>
+ <artifactId>sonar-process</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-server-app</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-search</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-web</artifactId>
<version>${project.version}</version>
<type>war</type>
+ <scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index 0ff37108e4a..b8b255f94bb 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -10,8 +10,8 @@
#--------------------------------------------------------------------------------------------------
# ELASTICSEARCH
-sonar.es.java_opts="-Xmx1G -Xms1G"
-sonar.web.java_opts="-Xmx128m -Xms128m"
+sonar.es.javaOpts=-Xmx256m -Xms256m
+sonar.web.java_opts=-Xmx76m
#--------------------------------------------------------------------------------------------------
diff --git a/sonar-application/src/main/assembly/conf/wrapper.conf b/sonar-application/src/main/assembly/conf/wrapper.conf
index 19be89188ed..4fd1f4d9363 100644
--- a/sonar-application/src/main/assembly/conf/wrapper.conf
+++ b/sonar-application/src/main/assembly/conf/wrapper.conf
@@ -1,19 +1,19 @@
# Java Additional Parameters
wrapper.java.additional.1=-Djava.awt.headless=true
-wrapper.java.additional.2=-XX:MaxPermSize=160m
+#wrapper.java.additional.2=-XX:MaxPermSize=160m
wrapper.java.additional.3=-XX:+HeapDumpOnOutOfMemoryError
wrapper.java.additional.4=-Dfile.encoding=UTF-8
wrapper.java.additional.5=-Djruby.management.enabled=false
# Maximum amount of memory of Java VM
-wrapper.java.additional.6=-Xmx1024M
+wrapper.java.additional.2=-Xmx32M
# RECOMMENDED : uncomment if Java Virtual Machine is a JDK but not a JRE. To know which JVM you use, execute
# 'java -version'. JDK displays 'Server VM'.
#wrapper.java.additional.7=-server
# Initial JVM heap size (in MB)
-wrapper.java.initmemory=256
+wrapper.java.initmemory=16
#********************************************************************
# Wrapper Java Properties
@@ -42,7 +42,7 @@ wrapper.java.classpath.6=../../extensions/jdbc-driver/mssql/*.jar
wrapper.java.library.path.1=./lib
# Application parameters. Add parameters as needed starting from 1
-wrapper.app.parameter.1=org.sonar.application.StartServer
+wrapper.app.parameter.1=org.sonar.application.ForkProcesses
# Do not touch the following property. Max memory is set with -Xmx (see above).
# See https://jira.codehaus.org/browse/SONAR-5204
diff --git a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
index a677f8bcf11..ceca36c6718 100644
--- a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
+++ b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java
@@ -34,7 +34,7 @@ class EmbeddedTomcat {
private final Env env;
private Tomcat tomcat = null;
private Thread hook = null;
- private boolean stopping = false;
+ private boolean stopping = false, ready = false;
EmbeddedTomcat(Env env) {
this.env = env;
@@ -71,6 +71,7 @@ class EmbeddedTomcat {
Webapp.configure(tomcat, env, props);
tomcat.start();
addShutdownHook();
+ ready = true;
tomcat.getServer().await();
// Shutdown command received
@@ -102,6 +103,7 @@ class EmbeddedTomcat {
}
tomcat = null;
stopping = false;
+ ready = false;
File tempDir = env.file(TEMP_RELATIVE_PATH);
FileUtils.deleteQuietly(tempDir);
@@ -117,6 +119,10 @@ class EmbeddedTomcat {
}
}
+ boolean isReady( ){
+ return ready;
+ }
+
int port() {
Connector[] connectors = tomcat.getService().findConnectors();
if (connectors.length > 0) {
diff --git a/sonar-application/src/main/java/org/sonar/application/ForkProcesses.java b/sonar-application/src/main/java/org/sonar/application/ForkProcesses.java
new file mode 100644
index 00000000000..bc6339d3f48
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/ForkProcesses.java
@@ -0,0 +1,104 @@
+/*
+ * 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.application;
+
+import org.sonar.process.Monitor;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessWrapper;
+
+import javax.annotation.Nullable;
+
+public class ForkProcesses {
+ private Monitor monitor;
+ private final Thread shutdownHook;
+ private ProcessWrapper elasticsearch;
+ private ProcessWrapper server;
+
+ public ForkProcesses() throws Exception {
+ Installation installation = new Installation();
+
+ String esPort = installation.prop("sonar.es.node.port", null);
+ if (esPort == null) {
+ esPort = String.valueOf(NetworkUtils.freePort());
+ installation.setProp("sonar.es.node.port", esPort);
+ }
+ installation.setProp("sonar.es.type", "TRANSPORT");
+
+ shutdownHook = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ monitor.interrupt();
+ terminateAndWait(elasticsearch);
+ terminateAndWait(server);
+ }
+ });
+
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+
+ elasticsearch = new ProcessWrapper(
+ installation.homeDir().getAbsolutePath(),
+ installation.prop("sonar.es.javaOpts", "-server -Xmx256m -Xms128m -Xss256k -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"),
+ "org.sonar.search.ElasticSearch",
+ installation.props(),
+ "ES",
+ installation.starPath("lib/search"));
+
+
+ server = new ProcessWrapper(
+ installation.homeDir().getAbsolutePath(),
+ installation.prop("sonar.web.javaOpts", "-Xmx768m -server -XX:MaxPermSize=160m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Djruby.management.enabled=false"),
+ "org.sonar.server.app.ServerProcess",
+ installation.props(),
+ "SQ",
+ installation.starPath("lib"));
+
+ monitor = new Monitor();
+ monitor.registerProcess(elasticsearch);
+ monitor.registerProcess(server);
+ monitor.start();
+ try {
+ monitor.join();
+ } catch (InterruptedException e) {
+ stop(true);
+ }
+ stop(true);
+ }
+
+ public void stop(boolean waitForCompletion) {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ shutdownHook.start();
+ if (waitForCompletion) {
+ try {
+ shutdownHook.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ private void terminateAndWait(@Nullable ProcessWrapper process) {
+ if (process != null && process.getThread() != null) {
+ process.terminate();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ new ForkProcesses();
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/Installation.java b/sonar-application/src/main/java/org/sonar/application/Installation.java
new file mode 100644
index 00000000000..41b87b60b15
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/Installation.java
@@ -0,0 +1,161 @@
+/*
+ * 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.application;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class Installation {
+
+ // guessed from location of sonar-application.jar
+ private final File homeDir;
+ private final File tempDir, dataDir, logsDir, webDir;
+ private final Map<String, String> props = new HashMap<String, String>();
+
+ Installation() throws URISyntaxException, IOException {
+ // TODO make it configurable with sonar.path.home ?
+ // lib/sonar-application.jar
+ File appJar = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
+ homeDir = appJar.getParentFile().getParentFile();
+
+ Properties p = new Properties();
+ File propsFile = new File(homeDir, "conf/sonar.properties");
+ if (propsFile.exists()) {
+ FileReader reader = new FileReader(propsFile);
+ try {
+ p.load(reader);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ }
+ p.putAll(System.getenv());
+ p.putAll(System.getProperties());
+ p = ConfigurationUtils.interpolateEnvVariables(p);
+ for (Map.Entry<Object, Object> entry : p.entrySet()) {
+ Object val = entry.getValue();
+ if (val != null) {
+ this.props.put(entry.getKey().toString(), val.toString());
+ }
+ }
+
+ props.put("sonar.path.home", homeDir.getAbsolutePath());
+ this.dataDir = existingDir("sonar.path.data", "data");
+ this.tempDir = freshDir("sonar.path.temp", "temp");
+ this.logsDir = existingDir("sonar.path.logs", "logs");
+ this.webDir = existingDir("sonar.path.web", "web");
+ }
+
+ File homeDir() {
+ return homeDir;
+ }
+
+ File esDir() {
+ return new File(homeDir, "data/es");
+ }
+
+ File webDir() {
+ return webDir;
+ }
+
+ File tempDir() {
+ return tempDir;
+ }
+
+ String starPath(String relativePath) {
+ File dir = new File(homeDir, relativePath);
+ return FilenameUtils.concat(dir.getAbsolutePath(), "*");
+ }
+
+ private File freshDir(String propKey, String defaultRelativePath) throws IOException {
+ File dir = configuredDir(propKey, defaultRelativePath);
+ FileUtils.deleteQuietly(dir);
+ FileUtils.forceMkdir(dir);
+ return dir;
+ }
+
+ private File existingDir(String propKey, String defaultRelativePath) throws IOException {
+ File dir = configuredDir(propKey, defaultRelativePath);
+ if (!dir.exists()) {
+ // TODO replace by MessageException
+ throw new IllegalStateException(String.format("Directory does not exist: %s. Please check property %s", dir.getAbsolutePath(), propKey));
+ }
+ if (!dir.isDirectory()) {
+ // TODO replace by MessageException
+ throw new IllegalStateException(String.format("Not a directory: %s. Please check property %s", dir.getAbsolutePath(), propKey));
+ }
+ return dir;
+ }
+
+ private File configuredDir(String propKey, String defaultRelativePath) {
+ String path = prop(propKey, defaultRelativePath);
+ File d = new File(path);
+ if (!d.isAbsolute()) {
+ d = new File(homeDir, path);
+ }
+ props.put(propKey, d.getAbsolutePath());
+ return d;
+ }
+
+ Map<String, String> props() {
+ return props;
+ }
+
+ @CheckForNull
+ String prop(String key, @Nullable String defaultValue) {
+ String s = props.get(key);
+ return s != null ? s : defaultValue;
+ }
+
+ @CheckForNull
+ Integer propAsInt(String key) {
+ String s = prop(key, null);
+ if (s != null && !"".equals(s)) {
+ try {
+ return Integer.parseInt(s);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(String.format("Value of property %s is not an integer: %s", key, s), e);
+ }
+ }
+ return null;
+ }
+
+ void setProp(String key, String value) {
+ props.put(key, value);
+ }
+
+ void logInfo(String message) {
+ System.out.println(message);
+ }
+
+ void logError(String message) {
+ System.err.println(message);
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/Webapp.java b/sonar-application/src/main/java/org/sonar/application/Webapp.java
index 8d5f5a4590a..082ebdecdea 100644
--- a/sonar-application/src/main/java/org/sonar/application/Webapp.java
+++ b/sonar-application/src/main/java/org/sonar/application/Webapp.java
@@ -37,6 +37,7 @@ class Webapp {
context.setConfigFile(env.file("web/META-INF/context.xml").toURI().toURL());
context.addParameter(PROPERTY_LOG_PROFILING_LEVEL, props.of(PROPERTY_LOG_PROFILING_LEVEL, "NONE"));
context.addParameter(PROPERTY_LOG_CONSOLE, props.of(PROPERTY_LOG_CONSOLE, "false"));
+
configureRailsMode(props, context);
context.setJarScanner(new NullJarScanner());
diff --git a/sonar-application/src/main/resources/logback.xml b/sonar-application/src/main/resources/logback.xml
index 259b15ff591..067d353bdd5 100644
--- a/sonar-application/src/main/resources/logback.xml
+++ b/sonar-application/src/main/resources/logback.xml
@@ -40,9 +40,19 @@
<appender-ref ref="CONSOLE"/>
</logger>
+ <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <pattern>
+ %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
<root>
- <level value="INFO"/>
+ <level value="DEBUG"/>
+ <appender-ref ref="CONSOLE"/>
<appender-ref ref="LOGFILE"/>
</root>
+
</configuration>