Skip to content

Commit

Permalink
SONAR-6740 refactor configuration of Tomcat contexts
Browse files Browse the repository at this point in the history
It allows to remove some warnings on static context when
 server fails to start.
  • Loading branch information
Simon Brandhof committed Feb 5, 2016
1 parent 8115b23 commit 39cc96f
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 207 deletions.
Expand Up @@ -56,7 +56,7 @@ void start() {
tomcat.getHost().setDeployOnStartup(true); tomcat.getHost().setDeployOnStartup(true);
new TomcatAccessLog().configure(tomcat, props); new TomcatAccessLog().configure(tomcat, props);
TomcatConnectors.configure(tomcat, props); TomcatConnectors.configure(tomcat, props);
webappContext = Webapp.configure(tomcat, props); webappContext = new TomcatContexts().configure(tomcat, props);
try { try {
tomcat.start(); tomcat.start();
new TomcatStartupLogs(props, Loggers.get(getClass())).log(tomcat); new TomcatStartupLogs(props, Loggers.get(getClass())).log(tomcat);
Expand Down
Expand Up @@ -19,34 +19,73 @@
*/ */
package org.sonar.server.app; package org.sonar.server.app;


import com.google.common.annotations.VisibleForTesting;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletException;
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat; 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.api.utils.log.Loggers;
import org.sonar.process.ProcessProperties; import org.sonar.process.ProcessProperties;
import org.sonar.process.Props; 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 JRUBY_MAX_RUNTIMES = "jruby.max.runtimes";
private static final String RAILS_ENV = "rails.env"; private static final String RAILS_ENV = "rails.env";
private static final String ROOT_CONTEXT_PATH = ""; 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 { try {
// URL /deploy must serve files deployed during startup into DATA_DIR/web/deploy fs.createOrCleanupDir(dir);
new WebDeployContext().configureTomcat(tomcat, props); } 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.setClearReferencesHttpClientKeepAliveThread(false);
context.setClearReferencesStatic(false); context.setClearReferencesStatic(false);
context.setClearReferencesStopThreads(false); context.setClearReferencesStopThreads(false);
Expand All @@ -61,16 +100,9 @@ static StandardContext configure(Tomcat tomcat, Props props) {
context.setUseNaming(false); context.setUseNaming(false);
context.setDelegate(true); context.setDelegate(true);
context.setJarScanner(new NullJarScanner()); 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; return context;

} catch (ServletException e) {
} catch (Exception e) { throw new IllegalStateException("Fail to configure webapp from " + dir, e);
throw new IllegalStateException("Fail to configure webapp", e);
} }
} }


Expand All @@ -82,19 +114,29 @@ static void configureRails(Props props, Context context) {
if (props.valueAsBoolean("sonar.web.dev", false)) { if (props.valueAsBoolean("sonar.web.dev", false)) {
context.addParameter(RAILS_ENV, "development"); context.addParameter(RAILS_ENV, "development");
context.addParameter(JRUBY_MAX_RUNTIMES, "3"); 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 { } else {
context.addParameter(RAILS_ENV, "production"); context.addParameter(RAILS_ENV, "production");
context.addParameter(JRUBY_MAX_RUNTIMES, "1"); context.addParameter(JRUBY_MAX_RUNTIMES, "1");
} }
} }


static String webappPath(Props props) { static File webappDir(Props props) {
String webDir = props.value("sonar.web.dev.sources"); String devDir = props.value("sonar.web.dev.sources");
if (StringUtils.isEmpty(webDir)) { File dir;
webDir = new File(props.value(ProcessProperties.PATH_HOME), "web").getAbsolutePath(); 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;
} }
} }

This file was deleted.

Expand Up @@ -41,7 +41,7 @@
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.process.ProcessProperties; 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;
import static org.sonar.api.CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE; import static org.sonar.api.CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE;
Expand Down Expand Up @@ -84,7 +84,7 @@ public void start() {
throw new IllegalStateException("SonarQube home directory is not valid"); 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)); LOG.info("SonarQube {}", Joiner.on(" / ").skipNulls().join("Server", version, implementationBuild));


Expand Down
Expand Up @@ -25,80 +25,106 @@
import org.apache.catalina.Context; import org.apache.catalina.Context;
import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.commons.io.FileUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.sonar.process.ProcessProperties; import org.sonar.process.ProcessProperties;
import org.sonar.process.Props; import org.sonar.process.Props;


import static org.assertj.core.api.Assertions.assertThat; 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.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.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;


public class WebappTest { public class TomcatContextsTest {


@Rule @Rule
public TemporaryFolder temp = new TemporaryFolder(); public TemporaryFolder temp = new TemporaryFolder();


Props props = new Props(new Properties()); @Rule
public ExpectedException expectedException = ExpectedException.none();


@Before Tomcat tomcat = mock(Tomcat.class);
public void initDataDir() throws Exception { Properties props = new Properties();
props.set(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath());
}


@Test @Before
public void fail_on_error() throws Exception { public void setUp() throws Exception {
File webDir = temp.newFolder("web"); props.setProperty(ProcessProperties.PATH_DATA, temp.newFolder("data").getAbsolutePath());

when(tomcat.addWebapp(anyString(), anyString())).thenReturn(mock(StandardContext.class));
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");
}
} }


@Test @Test
public void configure_context() throws Exception { public void configure_root_webapp() throws Exception {
props.set("foo", "bar"); props.setProperty("foo", "bar");
StandardContext context = mock(StandardContext.class); StandardContext context = mock(StandardContext.class);
Tomcat tomcat = mock(Tomcat.class);
when(tomcat.addWebapp(anyString(), anyString())).thenReturn(context); when(tomcat.addWebapp(anyString(), anyString())).thenReturn(context);


Webapp.configure(tomcat, props); new TomcatContexts().configure(tomcat, new Props(props));


// configure webapp with properties // configure webapp with properties
verify(context).addParameter("foo", "bar"); verify(context).addParameter("foo", "bar");
} }


@Test @Test
public void configure_rails_dev_mode() { public void configure_rails_dev_mode() {
props.set("sonar.web.dev", "true"); props.setProperty("sonar.web.dev", "true");
Context context = mock(Context.class); 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("jruby.max.runtimes", "3");
verify(context).addParameter("rails.env", "development"); verify(context).addParameter("rails.env", "development");
} }


@Test @Test
public void configure_production_mode() { public void configure_rails_production_mode() {
props.set("sonar.web.dev", "false"); props.setProperty("sonar.web.dev", "false");
Context context = mock(Context.class); 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("jruby.max.runtimes", "1");
verify(context).addParameter("rails.env", "production"); 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);

}
} }

0 comments on commit 39cc96f

Please sign in to comment.