It allows to remove some warnings on static context when server fails to start.tags/5.4-RC1
@@ -56,7 +56,7 @@ class EmbeddedTomcat { | |||
tomcat.getHost().setDeployOnStartup(true); | |||
new TomcatAccessLog().configure(tomcat, props); | |||
TomcatConnectors.configure(tomcat, props); | |||
webappContext = Webapp.configure(tomcat, props); | |||
webappContext = new TomcatContexts().configure(tomcat, props); | |||
try { | |||
tomcat.start(); | |||
new TomcatStartupLogs(props, Loggers.get(getClass())).log(tomcat); |
@@ -19,34 +19,73 @@ | |||
*/ | |||
package org.sonar.server.app; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.Map; | |||
import javax.servlet.ServletException; | |||
import org.apache.catalina.Context; | |||
import org.apache.catalina.core.StandardContext; | |||
import org.apache.catalina.startup.Tomcat; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import static java.lang.String.format; | |||
import static org.apache.commons.lang.StringUtils.isEmpty; | |||
/** | |||
* Configures webapp into Tomcat | |||
* Configures Tomcat contexts: | |||
* <ul> | |||
* <li>/deploy delivers the plugins required by analyzers. It maps directory ${sonar.path.data}/web/deploy.</li> | |||
* <li>/ is the regular webapp</li> | |||
* </ul> | |||
*/ | |||
class Webapp { | |||
public class TomcatContexts { | |||
private static final String JRUBY_MAX_RUNTIMES = "jruby.max.runtimes"; | |||
private static final String RAILS_ENV = "rails.env"; | |||
private static final String ROOT_CONTEXT_PATH = ""; | |||
public static final String WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR = "web/deploy"; | |||
private final Fs fs; | |||
private Webapp() { | |||
public TomcatContexts() { | |||
this.fs = new Fs(); | |||
} | |||
static StandardContext configure(Tomcat tomcat, Props props) { | |||
@VisibleForTesting | |||
TomcatContexts(Fs fs) { | |||
this.fs = fs; | |||
} | |||
public StandardContext configure(Tomcat tomcat, Props props) { | |||
addStaticDir(tomcat, "/deploy", new File(props.nonNullValueAsFile(ProcessProperties.PATH_DATA), WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR)); | |||
StandardContext webapp = addContext(tomcat, ROOT_CONTEXT_PATH, webappDir(props)); | |||
configureRails(props, webapp); | |||
for (Map.Entry<Object, Object> entry : props.rawProperties().entrySet()) { | |||
String key = entry.getKey().toString(); | |||
webapp.addParameter(key, entry.getValue().toString()); | |||
} | |||
return webapp; | |||
} | |||
@VisibleForTesting | |||
StandardContext addStaticDir(Tomcat tomcat, String contextPath, File dir) { | |||
try { | |||
// URL /deploy must serve files deployed during startup into DATA_DIR/web/deploy | |||
new WebDeployContext().configureTomcat(tomcat, props); | |||
fs.createOrCleanupDir(dir); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(format("Fail to create or clean-up directory %s", dir.getAbsolutePath()), e); | |||
} | |||
StandardContext context = (StandardContext) tomcat.addWebapp(ROOT_CONTEXT_PATH, webappPath(props)); | |||
return addContext(tomcat, contextPath, dir); | |||
} | |||
private StandardContext addContext(Tomcat tomcat, String contextPath, File dir) { | |||
try { | |||
StandardContext context = (StandardContext) tomcat.addWebapp(contextPath, dir.getAbsolutePath()); | |||
context.setClearReferencesHttpClientKeepAliveThread(false); | |||
context.setClearReferencesStatic(false); | |||
context.setClearReferencesStopThreads(false); | |||
@@ -61,16 +100,9 @@ class Webapp { | |||
context.setUseNaming(false); | |||
context.setDelegate(true); | |||
context.setJarScanner(new NullJarScanner()); | |||
configureRails(props, context); | |||
for (Map.Entry<Object, Object> entry : props.rawProperties().entrySet()) { | |||
String key = entry.getKey().toString(); | |||
context.addParameter(key, entry.getValue().toString()); | |||
} | |||
return context; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to configure webapp", e); | |||
} catch (ServletException e) { | |||
throw new IllegalStateException("Fail to configure webapp from " + dir, e); | |||
} | |||
} | |||
@@ -82,19 +114,29 @@ class Webapp { | |||
if (props.valueAsBoolean("sonar.web.dev", false)) { | |||
context.addParameter(RAILS_ENV, "development"); | |||
context.addParameter(JRUBY_MAX_RUNTIMES, "3"); | |||
Loggers.get(Webapp.class).warn("WEB DEVELOPMENT MODE IS ENABLED - DO NOT USE FOR PRODUCTION USAGE"); | |||
Loggers.get(TomcatContexts.class).warn("WEB DEVELOPMENT MODE IS ENABLED - DO NOT USE FOR PRODUCTION USAGE"); | |||
} else { | |||
context.addParameter(RAILS_ENV, "production"); | |||
context.addParameter(JRUBY_MAX_RUNTIMES, "1"); | |||
} | |||
} | |||
static String webappPath(Props props) { | |||
String webDir = props.value("sonar.web.dev.sources"); | |||
if (StringUtils.isEmpty(webDir)) { | |||
webDir = new File(props.value(ProcessProperties.PATH_HOME), "web").getAbsolutePath(); | |||
static File webappDir(Props props) { | |||
String devDir = props.value("sonar.web.dev.sources"); | |||
File dir; | |||
if (isEmpty(devDir)) { | |||
dir = new File(props.value(ProcessProperties.PATH_HOME), "web"); | |||
} else { | |||
dir = new File(devDir); | |||
} | |||
Loggers.get(TomcatContexts.class).info("Webapp directory: {}", dir); | |||
return dir; | |||
} | |||
static class Fs { | |||
void createOrCleanupDir(File dir) throws IOException { | |||
FileUtils.forceMkdir(dir); | |||
FileUtils.cleanDirectory(dir); | |||
} | |||
Loggers.get(Webapp.class).info(String.format("Webapp directory: %s", webDir)); | |||
return webDir; | |||
} | |||
} |
@@ -1,63 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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 com.google.common.annotations.VisibleForTesting; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import javax.servlet.ServletException; | |||
import org.apache.catalina.startup.Tomcat; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import static java.lang.String.format; | |||
public class WebDeployContext { | |||
public static final String RELATIVE_DIR_IN_DATA = "web/deploy"; | |||
private final Fs fs; | |||
public WebDeployContext() { | |||
this(new Fs()); | |||
} | |||
@VisibleForTesting | |||
public WebDeployContext(Fs fs) { | |||
this.fs = fs; | |||
} | |||
public void configureTomcat(Tomcat tomcat, Props props) throws ServletException { | |||
File deployDir = new File(props.nonNullValueAsFile(ProcessProperties.PATH_DATA), RELATIVE_DIR_IN_DATA); | |||
try { | |||
fs.createOrCleanupDir(deployDir); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(format("Fail to create or clean-up directory %s", deployDir.getAbsolutePath()), e); | |||
} | |||
tomcat.addWebapp("/deploy", deployDir.getAbsolutePath()); | |||
} | |||
static class Fs { | |||
void createOrCleanupDir(File dir) throws IOException { | |||
FileUtils.forceMkdir(dir); | |||
FileUtils.cleanDirectory(dir); | |||
} | |||
} | |||
} |
@@ -41,7 +41,7 @@ import org.sonar.api.platform.Server; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.server.app.WebDeployContext; | |||
import org.sonar.server.app.TomcatContexts; | |||
import static org.sonar.api.CoreProperties.SERVER_BASE_URL; | |||
import static org.sonar.api.CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE; | |||
@@ -84,7 +84,7 @@ public final class ServerImpl extends Server implements Startable { | |||
throw new IllegalStateException("SonarQube home directory is not valid"); | |||
} | |||
deployDir = new File(settings.getString(ProcessProperties.PATH_DATA), WebDeployContext.RELATIVE_DIR_IN_DATA); | |||
deployDir = new File(settings.getString(ProcessProperties.PATH_DATA), TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR); | |||
LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, implementationBuild)); | |||
@@ -25,56 +25,47 @@ import java.util.Properties; | |||
import org.apache.catalina.Context; | |||
import org.apache.catalina.core.StandardContext; | |||
import org.apache.catalina.startup.Tomcat; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.junit.Assert.fail; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; | |||
import static org.mockito.Mockito.doThrow; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
public class WebappTest { | |||
public class TomcatContextsTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
Props props = new Props(new Properties()); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Before | |||
public void initDataDir() throws Exception { | |||
props.set(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath()); | |||
} | |||
Tomcat tomcat = mock(Tomcat.class); | |||
Properties props = new Properties(); | |||
@Test | |||
public void fail_on_error() throws Exception { | |||
File webDir = temp.newFolder("web"); | |||
Tomcat tomcat = mock(Tomcat.class, RETURNS_DEEP_STUBS); | |||
when(tomcat.addContext("", webDir.getAbsolutePath())).thenThrow(new NullPointerException()); | |||
try { | |||
Webapp.configure(tomcat, props); | |||
fail(); | |||
} catch (IllegalStateException e) { | |||
assertThat(e).hasMessage("Fail to configure webapp"); | |||
} | |||
@Before | |||
public void setUp() throws Exception { | |||
props.setProperty(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath()); | |||
when(tomcat.addWebapp(anyString(), anyString())).thenReturn(mock(StandardContext.class)); | |||
} | |||
@Test | |||
public void configure_context() throws Exception { | |||
props.set("foo", "bar"); | |||
public void configure_root_webapp() throws Exception { | |||
props.setProperty("foo", "bar"); | |||
StandardContext context = mock(StandardContext.class); | |||
Tomcat tomcat = mock(Tomcat.class); | |||
when(tomcat.addWebapp(anyString(), anyString())).thenReturn(context); | |||
Webapp.configure(tomcat, props); | |||
new TomcatContexts().configure(tomcat, new Props(props)); | |||
// configure webapp with properties | |||
verify(context).addParameter("foo", "bar"); | |||
@@ -82,23 +73,58 @@ public class WebappTest { | |||
@Test | |||
public void configure_rails_dev_mode() { | |||
props.set("sonar.web.dev", "true"); | |||
props.setProperty("sonar.web.dev", "true"); | |||
Context context = mock(Context.class); | |||
Webapp.configureRails(props, context); | |||
new TomcatContexts().configureRails(new Props(props), context); | |||
verify(context).addParameter("jruby.max.runtimes", "3"); | |||
verify(context).addParameter("rails.env", "development"); | |||
} | |||
@Test | |||
public void configure_production_mode() { | |||
props.set("sonar.web.dev", "false"); | |||
public void configure_rails_production_mode() { | |||
props.setProperty("sonar.web.dev", "false"); | |||
Context context = mock(Context.class); | |||
Webapp.configureRails(props, context); | |||
new TomcatContexts().configureRails(new Props(props), context); | |||
verify(context).addParameter("jruby.max.runtimes", "1"); | |||
verify(context).addParameter("rails.env", "production"); | |||
} | |||
@Test | |||
public void create_dir_and_configure_static_directory() throws Exception { | |||
File dir = temp.newFolder(); | |||
dir.delete(); | |||
new TomcatContexts().addStaticDir(tomcat, "/deploy", dir); | |||
assertThat(dir).isDirectory().exists(); | |||
verify(tomcat).addWebapp("/deploy", dir.getAbsolutePath()); | |||
} | |||
@Test | |||
public void cleanup_static_directory_if_already_exists() throws Exception { | |||
File dir = temp.newFolder(); | |||
FileUtils.touch(new File(dir, "foo.txt")); | |||
new TomcatContexts().addStaticDir(tomcat, "/deploy", dir); | |||
assertThat(dir).isDirectory().exists(); | |||
assertThat(dir.listFiles()).isEmpty(); | |||
} | |||
@Test | |||
public void fail_if_static_directory_can_not_be_initialized() throws Exception { | |||
File dir = temp.newFolder(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Fail to create or clean-up directory " + dir.getAbsolutePath()); | |||
TomcatContexts.Fs fs = mock(TomcatContexts.Fs.class); | |||
doThrow(new IOException()).when(fs).createOrCleanupDir(any(File.class)); | |||
new TomcatContexts(fs).addStaticDir(tomcat, "/deploy", dir); | |||
} | |||
} |
@@ -1,87 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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 java.io.File; | |||
import java.io.IOException; | |||
import java.util.Properties; | |||
import org.apache.catalina.startup.Tomcat; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Matchers.any; | |||
import static org.mockito.Mockito.doThrow; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
public class WebDeployContextTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
Tomcat tomcat = mock(Tomcat.class); | |||
Properties props = new Properties(); | |||
@Test | |||
public void create_dir_and_configure_tomcat_context() throws Exception { | |||
File dataDir = temp.newFolder(); | |||
props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath()); | |||
new WebDeployContext().configureTomcat(tomcat, new Props(props)); | |||
File deployDir = new File(dataDir, "web/deploy"); | |||
assertThat(deployDir).isDirectory().exists(); | |||
verify(tomcat).addWebapp("/deploy", deployDir.getAbsolutePath()); | |||
} | |||
@Test | |||
public void cleanup_directory_if_already_exists() throws Exception { | |||
File dataDir = temp.newFolder(); | |||
File deployDir = new File(dataDir, "web/deploy"); | |||
FileUtils.touch(new File(deployDir, "foo.txt")); | |||
props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath()); | |||
new WebDeployContext().configureTomcat(tomcat, new Props(props)); | |||
assertThat(deployDir).isDirectory().exists(); | |||
assertThat(deployDir.listFiles()).isEmpty(); | |||
} | |||
@Test | |||
public void fail_if_directory_can_not_be_initialized() throws Exception { | |||
File dataDir = temp.newFolder(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("Fail to create or clean-up directory " + dataDir.getAbsolutePath()); | |||
props.setProperty(ProcessProperties.PATH_DATA, dataDir.getAbsolutePath()); | |||
WebDeployContext.Fs fs = mock(WebDeployContext.Fs.class); | |||
doThrow(new IOException()).when(fs).createOrCleanupDir(any(File.class)); | |||
new WebDeployContext(fs).configureTomcat(tomcat, new Props(props)); | |||
} | |||
} |