diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2014-07-22 00:02:51 +0200 |
---|---|---|
committer | Stephane Gamard <stephane.gamard@searchbox.com> | 2014-07-23 15:34:03 +0200 |
commit | 313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f (patch) | |
tree | 6fc817743c48da2d944b8b04cb75ada6207bba87 | |
parent | d0f9dca7b2d3dd27ab0b43ecdd9c0ea93d4ae734 (diff) | |
download | sonarqube-313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f.tar.gz sonarqube-313c75ea72b0bdf59b91b1bb1712c01d9ce06b4f.zip |
Experimental fork
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> |