From 39cc96f13fff24c17bb9b1a37311cd37281f6e03 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 4 Feb 2016 22:42:10 +0100 Subject: [PATCH] SONAR-6740 refactor configuration of Tomcat contexts It allows to remove some warnings on static context when server fails to start. --- .../org/sonar/server/app/EmbeddedTomcat.java | 2 +- .../app/{Webapp.java => TomcatContexts.java} | 90 ++++++++++++++----- .../sonar/server/app/WebDeployContext.java | 63 ------------- .../org/sonar/server/platform/ServerImpl.java | 4 +- ...ebappTest.java => TomcatContextsTest.java} | 86 +++++++++++------- .../server/app/WebDeployContextTest.java | 87 ------------------ 6 files changed, 125 insertions(+), 207 deletions(-) rename server/sonar-server/src/main/java/org/sonar/server/app/{Webapp.java => TomcatContexts.java} (51%) delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/app/WebDeployContext.java rename server/sonar-server/src/test/java/org/sonar/server/app/{WebappTest.java => TomcatContextsTest.java} (51%) delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/app/WebDeployContextTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java index 5f3bad375c30..39b5d5c1e0a4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java @@ -56,7 +56,7 @@ void start() { 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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java similarity index 51% rename from server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java rename to server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java index bee8e2063dce..1d25678edef7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/Webapp.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java @@ -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: + * */ -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 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 @@ static StandardContext configure(Tomcat tomcat, Props props) { context.setUseNaming(false); context.setDelegate(true); context.setJarScanner(new NullJarScanner()); - configureRails(props, context); - - for (Map.Entry 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 @@ static void configureRails(Props props, Context context) { 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; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebDeployContext.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebDeployContext.java deleted file mode 100644 index 688271a79ac3..000000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/app/WebDeployContext.java +++ /dev/null @@ -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); - } - } -} 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 cf6bd157ebad..3ec74f8c3065 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 @@ -41,7 +41,7 @@ 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 void start() { 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)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/WebappTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatContextsTest.java similarity index 51% rename from server/sonar-server/src/test/java/org/sonar/server/app/WebappTest.java rename to server/sonar-server/src/test/java/org/sonar/server/app/TomcatContextsTest.java index 8b8a2f2a935e..e54244c43042 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/app/WebappTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatContextsTest.java @@ -25,56 +25,47 @@ 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 void configure_context() throws Exception { @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); + + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/WebDeployContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/WebDeployContextTest.java deleted file mode 100644 index eac45d9d1fe4..000000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/app/WebDeployContextTest.java +++ /dev/null @@ -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)); - - } -}