@@ -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> |
@@ -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()); | |||
@@ -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(); | |||
@@ -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 |
@@ -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"); | |||
} | |||
} | |||
} | |||
} |
@@ -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> |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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"); | |||
} | |||
} | |||
} | |||
} |
@@ -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! | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} | |||
} |
@@ -151,32 +151,9 @@ | |||
<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> |
@@ -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"); | |||
} |
@@ -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")); | |||
} | |||
/** |
@@ -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); | |||
} | |||
} |
@@ -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"); | |||
} |
@@ -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() { |
@@ -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(); |
@@ -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> |
@@ -14,11 +14,21 @@ | |||
<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> |
@@ -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> |
@@ -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 | |||
#-------------------------------------------------------------------------------------------------- |
@@ -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 |
@@ -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) { |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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()); | |||
@@ -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> |