From: Simon Brandhof Date: Tue, 26 Jul 2016 12:14:33 +0000 (+0200) Subject: SONAR-7899 Refactor server file system X-Git-Tag: 6.1-RC1~489 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7ce0fb39f58c6fd4d01075944e1756909fd6abc3;p=sonarqube.git SONAR-7899 Refactor server file system - new interface org.sonar.server.platform.ServerFileSystem - deprecate org.sonar.api.platform.Server#getDeployDir() --- diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java index a9d64dd3c37..67587fbd760 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java @@ -21,7 +21,7 @@ package org.sonar.ce.container; import java.io.File; import org.apache.commons.io.FileUtils; -import org.sonar.api.platform.ServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.api.utils.ZipUtils; import org.sonar.core.platform.ExplodedPlugin; import org.sonar.core.platform.PluginInfo; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java index 0f24f3d6b1d..540f93f2fe8 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java @@ -33,7 +33,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginLoader; import org.sonar.core.platform.PluginRepository; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -48,7 +48,7 @@ public class CePluginRepository implements PluginRepository, Startable { private static final String[] JAR_FILE_EXTENSIONS = new String[] {"jar"}; private static final String NOT_STARTED_YET = "not started yet"; - private final DefaultServerFileSystem fs; + private final ServerFileSystem fs; private final PluginLoader loader; private final AtomicBoolean started = new AtomicBoolean(false); @@ -56,7 +56,7 @@ public class CePluginRepository implements PluginRepository, Startable { private final Map pluginInfosByKeys = new HashMap<>(); private final Map pluginInstancesByKeys = new HashMap<>(); - public CePluginRepository(DefaultServerFileSystem fs, PluginLoader loader) { + public CePluginRepository(ServerFileSystem fs, PluginLoader loader) { this.fs = fs; this.loader = loader; } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index b3d4233e5c9..bab0a7282d1 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -70,9 +70,9 @@ import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentService; import org.sonar.server.computation.CeModule; -import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule; import org.sonar.server.computation.CeQueueModule; import org.sonar.server.computation.queue.PurgeCeActivities; +import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule; import org.sonar.server.computation.taskprocessor.CeTaskProcessorModule; import org.sonar.server.debt.DebtModelPluginRepository; import org.sonar.server.debt.DebtRulesXMLImporter; @@ -99,9 +99,9 @@ import org.sonar.server.notification.NotificationService; import org.sonar.server.notification.email.AlertsEmailTemplate; import org.sonar.server.notification.email.EmailNotificationChannel; import org.sonar.server.platform.DatabaseServerCompatibility; -import org.sonar.server.platform.DefaultServerFileSystem; import org.sonar.server.platform.DefaultServerUpgradeStatus; import org.sonar.server.platform.PersistentSettings; +import org.sonar.server.platform.ServerFileSystemImpl; import org.sonar.server.platform.ServerImpl; import org.sonar.server.platform.ServerLifecycleNotifier; import org.sonar.server.platform.ServerLogging; @@ -216,7 +216,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { DatabaseServerCompatibility.class, DatabaseVersion.class, PurgeProfiler.class, - DefaultServerFileSystem.class, + ServerFileSystemImpl.class, // no TempFolderCleaner.class, responsibility of Web Server new TempFolderProvider(), System2.INSTANCE, diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java index 4062a8747f3..59be2e53d21 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java @@ -24,7 +24,7 @@ import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.ServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.core.platform.ExplodedPlugin; import org.sonar.core.platform.PluginInfo; @@ -97,6 +97,16 @@ public class CePluginJarExploderTest { this.temp = temp; } + @Override + public File getDataDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getDeployDir() { + throw new UnsupportedOperationException(); + } + @Override public File getHomeDir() { throw new UnsupportedOperationException(); @@ -114,5 +124,30 @@ public class CePluginJarExploderTest { return tempDir; } + @Override + public File getDeployedPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getDownloadedPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getInstalledPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getBundledPluginsDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getPluginIndex() { + throw new UnsupportedOperationException(); + } + } } diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java index a7ebbc8eff3..09c044809bf 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java @@ -32,7 +32,7 @@ import org.mockito.Mockito; import org.sonar.api.Plugin; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginLoader; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -46,9 +46,9 @@ public class CePluginRepositoryTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); - PluginLoader pluginLoader = new DumbPluginLoader(); - CePluginRepository underTest = new CePluginRepository(fs, pluginLoader); + private ServerFileSystem fs = mock(ServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); + private PluginLoader pluginLoader = new DumbPluginLoader(); + private CePluginRepository underTest = new CePluginRepository(fs, pluginLoader); @After public void tearDown() { diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/BatchIndex.java b/server/sonar-server/src/main/java/org/sonar/server/batch/BatchIndex.java index 451ab800945..511c90baf40 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/BatchIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/BatchIndex.java @@ -30,8 +30,8 @@ import org.apache.commons.io.filefilter.HiddenFileFilter; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; import org.picocontainer.Startable; -import org.sonar.api.platform.Server; import org.sonar.api.server.ServerSide; +import org.sonar.server.platform.ServerFileSystem; /** * JAR files to be downloaded by sonar-runner. @@ -39,18 +39,18 @@ import org.sonar.api.server.ServerSide; @ServerSide public class BatchIndex implements Startable { - private final Server server; + private final ServerFileSystem fs; private String index; private File batchDir; - public BatchIndex(Server server) { - this.server = server; + public BatchIndex(ServerFileSystem fs) { + this.fs = fs; } @Override public void start() { StringBuilder sb = new StringBuilder(); - batchDir = new File(server.getRootDir(), "lib/batch"); + batchDir = new File(fs.getHomeDir(), "lib/batch"); if (batchDir.exists()) { Collection files = FileUtils.listFiles(batchDir, HiddenFileFilter.VISIBLE, FileFilterUtils.directoryFileFilter()); for (File file : files) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/filesystem/ComputationTempFolderProvider.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/filesystem/ComputationTempFolderProvider.java index c4425c0b43a..f810ee15b03 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/filesystem/ComputationTempFolderProvider.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/filesystem/ComputationTempFolderProvider.java @@ -26,9 +26,9 @@ import org.apache.commons.io.FileUtils; import org.picocontainer.ComponentLifecycle; import org.picocontainer.PicoContainer; import org.picocontainer.injectors.ProviderAdapter; -import org.sonar.api.platform.ServerFileSystem; import org.sonar.api.utils.TempFolder; import org.sonar.api.utils.internal.DefaultTempFolder; +import org.sonar.server.platform.ServerFileSystem; /** * Provides a TempFolder instance pointing to a directory dedicated to the processing of a specific item. diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ClassLoaderUtils.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ClassLoaderUtils.java deleted file mode 100644 index 4a067f9d248..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ClassLoaderUtils.java +++ /dev/null @@ -1,141 +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.platform; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.base.Throwables; -import com.google.common.collect.Lists; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.Collection; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.CharEncoding; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.utils.log.Loggers; - -/** - * @since 3.0 - */ -class ClassLoaderUtils { - - private ClassLoaderUtils() { - // only static methods - } - - static File copyResources(ClassLoader classLoader, String rootPath, File toDir, Function relocationFunction) { - Collection relativePaths = listFiles(classLoader, rootPath); - for (String relativePath : relativePaths) { - URL resource = classLoader.getResource(relativePath); - String filename = relocationFunction.apply(relativePath); - File toFile = new File(toDir, filename); - try { - FileUtils.copyURLToFile(resource, toFile); - } catch (IOException e) { - throw new IllegalStateException("Fail to extract " + relativePath + " to " + toFile.getAbsolutePath(), e); - } - } - - return toDir; - } - - /** - * Finds files within a given directory and its subdirectories - * - * @param classLoader - * @param rootPath the root directory, for example org/sonar/sqale - * @return a list of relative paths, for example {"org/sonar/sqale/foo/bar.txt}. Never null. - */ - static Collection listFiles(ClassLoader classLoader, String rootPath) { - return listResources(classLoader, rootPath, new Predicate() { - @Override - public boolean apply(@Nullable String path) { - return !StringUtils.endsWith(path, "/"); - } - }); - } - - /** - * Finds directories and files within a given directory and its subdirectories. - * - * @param classLoader - * @param rootPath the root directory, for example org/sonar/sqale, or a file in this root directory, for example org/sonar/sqale/index.txt - * @param predicate - * @return a list of relative paths, for example {"org/sonar/sqale", "org/sonar/sqale/foo", "org/sonar/sqale/foo/bar.txt}. Never null. - */ - static Collection listResources(ClassLoader classLoader, String rootPath, Predicate predicate) { - String jarPath = null; - JarFile jar = null; - try { - Collection paths = Lists.newArrayList(); - URL root = classLoader.getResource(rootPath); - if (root != null) { - checkJarFile(root); - - // Path of the root directory - // Examples : - // org/sonar/sqale/index.txt -> rootDirectory is org/sonar/sqale - // org/sonar/sqale/ -> rootDirectory is org/sonar/sqale - // org/sonar/sqale -> rootDirectory is org/sonar/sqale - String rootDirectory = rootPath; - if (StringUtils.substringAfterLast(rootPath, "/").indexOf('.') >= 0) { - rootDirectory = StringUtils.substringBeforeLast(rootPath, "/"); - } - //strip out only the JAR file - jarPath = root.getPath().substring(5, root.getPath().indexOf('!')); - jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8)); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - String name = entries.nextElement().getName(); - if (name.startsWith(rootDirectory) && predicate.apply(name)) { - paths.add(name); - } - } - } - return paths; - } catch (Exception e) { - throw Throwables.propagate(e); - } finally { - closeJar(jar, jarPath); - } - } - - private static void closeJar(JarFile jar, String jarPath) { - if (jar != null) { - try { - jar.close(); - } catch (Exception e) { - Loggers.get(ClassLoaderUtils.class).error("Fail to close JAR file: " + jarPath, e); - } - } - } - - private static void checkJarFile(URL root) { - if (!"jar".equals(root.getProtocol())) { - throw new IllegalStateException("Unsupported protocol: " + root.getProtocol()); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java deleted file mode 100644 index 3afca7f9f76..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ /dev/null @@ -1,124 +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.platform; - -import java.io.File; -import java.io.IOException; -import javax.annotation.CheckForNull; -import org.apache.commons.io.FileUtils; -import org.picocontainer.Startable; -import org.sonar.api.config.Settings; -import org.sonar.api.platform.Server; -import org.sonar.api.platform.ServerFileSystem; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.process.ProcessProperties; - -/** - * Introspect the filesystem and the classloader to get extension files at startup. - * - * @since 2.2 - */ -public class DefaultServerFileSystem implements ServerFileSystem, Startable { - - private static final Logger LOGGER = Loggers.get(DefaultServerFileSystem.class); - - private final Server server; - private final File homeDir; - private final File tempDir; - - public DefaultServerFileSystem(Settings settings, Server server) { - this.server = server; - this.homeDir = new File(settings.getString(ProcessProperties.PATH_HOME)); - this.tempDir = new File(settings.getString(ProcessProperties.PATH_TEMP)); - } - - /** - * for unit tests - */ - public DefaultServerFileSystem(File homeDir, File tempDir, Server server) { - this.homeDir = homeDir; - this.tempDir = tempDir; - this.server = server; - } - - @Override - public void start() { - LOGGER.info("SonarQube home: " + homeDir.getAbsolutePath()); - - File deployDir = getDeployDir(); - if (deployDir == null) { - throw new IllegalArgumentException("Web app directory does not exist"); - } - - File deprecated = getDeprecatedPluginsDir(); - try { - FileUtils.forceMkdir(deprecated); - org.sonar.core.util.FileUtils.cleanDirectory(deprecated); - } catch (IOException e) { - throw new IllegalStateException("The following directory can not be created: " + deprecated.getAbsolutePath(), e); - } - } - - @Override - public void stop() { - // do nothing - } - - @Override - public File getHomeDir() { - return homeDir; - } - - @Override - public File getTempDir() { - return tempDir; - } - - @CheckForNull - public File getDeployDir() { - return server.getDeployDir(); - } - - public File getDeployedPluginsDir() { - return new File(getDeployDir(), "plugins"); - } - - public File getDownloadedPluginsDir() { - return new File(getHomeDir(), "extensions/downloads"); - } - - public File getInstalledPluginsDir() { - return new File(getHomeDir(), "extensions/plugins"); - } - - public File getBundledPluginsDir() { - return new File(getHomeDir(), "lib/bundled-plugins"); - } - - public File getDeprecatedPluginsDir() { - return new File(getHomeDir(), "extensions/deprecated"); - } - - public File getPluginIndex() { - return new File(getDeployDir(), "plugins/index.txt"); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/MasterServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/MasterServletFilter.java deleted file mode 100644 index 9985abcc5b2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/MasterServletFilter.java +++ /dev/null @@ -1,136 +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.platform; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import java.io.IOException; -import java.util.Iterator; -import java.util.List; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.web.ServletFilter; - -/** - * Inspired by http://stackoverflow.com/a/7592883/229031 - */ -public class MasterServletFilter implements Filter { - - public static volatile MasterServletFilter INSTANCE; - private ServletFilter[] filters; - private FilterConfig config; - - public MasterServletFilter() { - if (INSTANCE != null) { - throw new IllegalStateException("Servlet filter " + getClass().getName() + " is already instantiated"); - } - INSTANCE = this; - } - - @Override - public void init(FilterConfig config) throws ServletException { - // Filters are already available in picocontainer unless a database migration is required. See - // org.sonar.server.startup.RegisterServletFilters. - init(config, Platform.getInstance().getContainer().getComponentsByType(ServletFilter.class)); - } - - void init(FilterConfig config, List filters) throws ServletException { - this.config = config; - initFilters(filters); - } - - public void initFilters(List filterExtensions) throws ServletException { - List filterList = Lists.newArrayList(); - for (ServletFilter extension : filterExtensions) { - try { - Loggers.get(MasterServletFilter.class).info(String.format("Initializing servlet filter %s [pattern=%s]", extension, extension.doGetPattern())); - extension.init(config); - filterList.add(extension); - } catch (Exception e) { - throw new IllegalStateException("Fail to initialize servlet filter: " + extension + ". Message: " + e.getMessage(), e); - } - } - filters = filterList.toArray(new ServletFilter[filterList.size()]); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { - HttpServletRequest hsr = (HttpServletRequest) request; - if (filters.length == 0) { - chain.doFilter(request, response); - } else { - String path = hsr.getRequestURI().replaceFirst(hsr.getContextPath(), ""); - GodFilterChain godChain = new GodFilterChain(chain); - - for (ServletFilter filter : filters) { - if (filter.doGetPattern().matches(path)) { - godChain.addFilter(filter); - } - } - godChain.doFilter(request, response); - } - } - - @Override - public void destroy() { - for (ServletFilter filter : filters) { - filter.destroy(); - } - } - - @VisibleForTesting - ServletFilter[] getFilters() { - return filters; - } - - private static final class GodFilterChain implements FilterChain { - private FilterChain chain; - private List filters = Lists.newLinkedList(); - private Iterator iterator; - - public GodFilterChain(FilterChain chain) { - this.chain = chain; - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { - if (iterator == null) { - iterator = filters.iterator(); - } - if (iterator.hasNext()) { - iterator.next().doFilter(request, response, this); - } else { - chain.doFilter(request, response); - } - } - - public void addFilter(Filter filter) { - Preconditions.checkState(iterator == null); - filters.add(filter); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java deleted file mode 100644 index 83a6a75c188..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/PlatformServletContextListener.java +++ /dev/null @@ -1,70 +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.platform; - -import com.google.common.base.Throwables; -import java.util.Enumeration; -import java.util.Properties; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -public final class PlatformServletContextListener implements ServletContextListener { - - static final String STARTED_ATTRIBUTE = "sonarqube.started"; - - @Override - public void contextInitialized(ServletContextEvent event) { - try { - Properties props = new Properties(); - ServletContext servletContext = event.getServletContext(); - Enumeration paramKeys = servletContext.getInitParameterNames(); - while (paramKeys.hasMoreElements()) { - String key = paramKeys.nextElement(); - props.put(key, servletContext.getInitParameter(key)); - } - Platform.getInstance().init(props, servletContext); - Platform.getInstance().doStart(); - event.getServletContext().setAttribute(STARTED_ATTRIBUTE, Boolean.TRUE); - - } catch (Throwable t) { - // Tomcat 7 "limitations": - // - server does not stop if webapp fails at startup - // - the second listener for jruby on rails is started even if this listener fails. It generates - // unexpected errors - stopQuietly(); - throw Throwables.propagate(t); - } - } - - private void stopQuietly() { - try { - Platform.getInstance().doStop(); - } catch (Exception e) { - // ignored, but an error during startup generally prevents pico to be correctly stopped - } - } - - @Override - public void contextDestroyed(ServletContextEvent event) { - Platform.getInstance().doStop(); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ProfilingFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ProfilingFilter.java deleted file mode 100644 index 9aa397f4d99..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ProfilingFilter.java +++ /dev/null @@ -1,115 +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.platform; - -import com.google.common.collect.ImmutableSet; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -import java.io.IOException; -import java.util.Set; - -/** - *

Profile HTTP requests using platform profiling utility.

- *

To avoid profiling of requests for static resources, the staticDirs - * filter parameter can be set in the servlet context descriptor. This parameter should - * contain a comma-separated list of paths, starting at the context root; - * requests on subpaths of these paths will not be profiled.

- * - * @since 4.1 - */ -public class ProfilingFilter implements Filter { - - private static final String CONFIG_SEPARATOR = ","; - private static final String URL_SEPARATOR = "/"; - - private static final String MESSAGE_WITH_QUERY = "%s %s?%s"; - private static final String MESSAGE_WITHOUT_QUERY = "%s %s"; - public static final org.sonar.api.utils.log.Logger Logger = Loggers.get("http"); - - private String contextRoot; - private Set staticResourceDirs; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - contextRoot = filterConfig.getServletContext().getContextPath(); - - String staticResourcesConfig = filterConfig.getInitParameter("staticDirs"); - if (StringUtils.isNotBlank(staticResourcesConfig)) { - staticResourceDirs = ImmutableSet.copyOf(staticResourcesConfig.split(CONFIG_SEPARATOR)); - } else { - staticResourceDirs = ImmutableSet.of(); - } - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (request instanceof HttpServletRequest) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - String requestUri = httpRequest.getRequestURI(); - String rootDir = getRootDir(requestUri); - - if (staticResourceDirs.contains(rootDir)) { - // Static resource, not profiled - chain.doFilter(request, response); - } else { - Profiler profiler = Profiler.createIfDebug(Logger).start(); - try { - chain.doFilter(request, response); - } finally { - if (profiler.isDebugEnabled()) { - String queryString = httpRequest.getQueryString(); - String message = String.format(queryString == null ? MESSAGE_WITHOUT_QUERY : MESSAGE_WITH_QUERY, httpRequest.getMethod(), requestUri, queryString); - profiler.stopDebug(message); - } - } - } - } else { - // Not an HTTP request, not profiled - chain.doFilter(request, response); - } - } - - private String getRootDir(String requestUri) { - String rootPath = ""; - String localPath = StringUtils.substringAfter(requestUri, contextRoot); - if (localPath.startsWith(URL_SEPARATOR)) { - int secondSlash = localPath.indexOf(URL_SEPARATOR, 1); - if (secondSlash > 0) { - rootPath = URL_SEPARATOR + localPath.substring(1, secondSlash); - } - } - return rootPath; - } - - @Override - public void destroy() { - // Nothing - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java deleted file mode 100644 index 2a5d0a3e7ad..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java +++ /dev/null @@ -1,126 +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.platform; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import java.io.File; -import java.io.IOException; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.picocontainer.Startable; -import org.sonar.api.Plugin; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.ServerFileSystem; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.core.platform.PluginInfo; -import org.sonar.core.platform.PluginRepository; - -/** - * Ruby on Rails requires the files to be on filesystem but not in Java classpath (JAR). This component extracts - * all the needed files from plugins and copy them to $SONAR_HOME/temp - * - * @since 3.0 - */ -public class RailsAppsDeployer implements Startable { - - private static final Logger LOG = Loggers.get(RailsAppsDeployer.class); - private static final String ROR_PATH = "org/sonar/ror/"; - - private final ServerFileSystem fs; - private final PluginRepository pluginRepository; - - public RailsAppsDeployer(ServerFileSystem fs, PluginRepository pluginRepository) { - this.fs = fs; - this.pluginRepository = pluginRepository; - } - - @Override - public void start() { - LOG.info("Deploying Ruby on Rails applications"); - File appsDir = prepareRailsDirectory(); - - for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { - String pluginKey = pluginInfo.getKey(); - Plugin plugin = pluginRepository.getPluginInstance(pluginKey); - try { - deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader()); - } catch (Exception e) { - throw new IllegalStateException(String.format("Fail to deploy Ruby on Rails application of plugin [%s]", pluginKey), e); - } - } - } - - @Override - public void stop() { - // do nothing - } - - @VisibleForTesting - File prepareRailsDirectory() { - File appsDir = new File(fs.getTempDir(), "ror"); - prepareDir(appsDir); - return appsDir; - } - - @VisibleForTesting - static void deployRailsApp(File appsDir, final String pluginKey, ClassLoader appClassLoader) { - if (hasRailsApp(pluginKey, appClassLoader)) { - LOG.info("Deploying app: " + pluginKey); - File appDir = new File(appsDir, pluginKey); - ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, new Function() { - @Override - public String apply(@Nullable String relativePath) { - // Relocate the deployed files : - // relativePath format is: org/sonar/ror/sqale/app/controllers/foo_controller.rb - // app path is: org/sonar/ror/sqale - // -> deployed file is app/controllers/foo_controller.rb - return StringUtils.substringAfter(relativePath, pluginKey + "/"); - } - }); - } - } - - private static String pathToRubyInitFile(String pluginKey) { - return ROR_PATH + pluginKey + "/init.rb"; - } - - @VisibleForTesting - static boolean hasRailsApp(String pluginKey, ClassLoader classLoader) { - return classLoader.getResource(pathToRubyInitFile(pluginKey)) != null; - } - - private void prepareDir(File appsDir) { - if (appsDir.exists() && appsDir.isDirectory()) { - try { - org.sonar.core.util.FileUtils.deleteDirectory(appsDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to delete temp directory: " + appsDir, e); - } - } - try { - FileUtils.forceMkdir(appsDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to create temp directory: " + appsDir, e); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RoutesFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RoutesFilter.java deleted file mode 100644 index 2cb7ad6d146..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/RoutesFilter.java +++ /dev/null @@ -1,68 +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.platform; - -import static java.lang.String.format; - -import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class RoutesFilter implements Filter { - - private static final String EMPTY = ""; - private static final String BATCH_WS = "/batch"; - private static final String API_SOURCES_WS = "/api/sources"; - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - String path = request.getRequestURI().replaceFirst(request.getContextPath(), EMPTY); - if (path.startsWith(BATCH_WS + "/") && path.endsWith(".jar")) { - // Scanner is still using /batch/file.jar url - response.sendRedirect(format("%s%s/file?name=%s", request.getContextPath(), BATCH_WS, path.replace(BATCH_WS + "/", EMPTY))); - } else if ("/batch_bootstrap/index".equals(path)) { - // Scanner is still using /batch_bootstrap url - response.sendRedirect(format("%s%s/index", request.getContextPath(), BATCH_WS)); - } else if (API_SOURCES_WS.equals(path)) { - // SONAR-7852 /api/sources?resource url is still used - response.sendRedirect(format("%s%s/index?%s", request.getContextPath(), API_SOURCES_WS, request.getQueryString())); - } else { - chain.doFilter(request, response); - } - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // Nothing - } - - @Override - public void destroy() { - // Nothing - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RubyRailsContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RubyRailsContextListener.java deleted file mode 100644 index 3f8fb0cd0e9..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/RubyRailsContextListener.java +++ /dev/null @@ -1,51 +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.platform; - -import com.google.common.base.Throwables; -import javax.servlet.ServletContextEvent; -import org.jruby.rack.RackApplicationFactory; -import org.jruby.rack.rails.RailsServletContextListener; -import org.jruby.rack.servlet.ServletRackContext; - -/** - * Overriding {@link RailsServletContextListener} allows to disable initialization of Ruby on Rails - * environment when Java components fail to start. - * See https://jira.sonarsource.com/browse/SONAR-6740 - */ -public class RubyRailsContextListener extends RailsServletContextListener { - - @Override - public void contextInitialized(ServletContextEvent event) { - if (event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE) != null) { - super.contextInitialized(event); - } - } - - // Always stop server when an error is raised during startup. - // By default Rack only logs an error (see org.jruby.rack.RackServletContextListener#handleInitializationException()). - // Rack propagates exceptions if the properties jruby.rack.exception or jruby.rack.error are set to true. - // Unfortunately we didn't succeed in defining these properties, so the method is overridden here. - // Initial need: SONAR-6171 - @Override - protected void handleInitializationException(Exception e, RackApplicationFactory factory, ServletRackContext rackContext) { - throw Throwables.propagate(e); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java deleted file mode 100644 index b929a8ce20f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/SecurityServletFilter.java +++ /dev/null @@ -1,78 +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.platform; - -import com.google.common.collect.ImmutableSet; -import java.io.IOException; -import java.util.Set; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * This servlet filter sets response headers that enable browser protection against several classes if Web attacks. - * The list of headers is mirrored in environment.rb as a workaround to Rack swallowing the headers.. - */ -public class SecurityServletFilter implements Filter { - - private static final Set ALLOWED_HTTP_METHODS = ImmutableSet.of("DELETE", "GET", "HEAD", "POST", "PUT"); - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // nothing - } - - @Override - public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { - doHttpFilter((HttpServletRequest) req, (HttpServletResponse) resp, chain); - } - - private static void doHttpFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException { - // SONAR-6881 Disable OPTIONS and TRACE methods - if (!ALLOWED_HTTP_METHODS.contains(httpRequest.getMethod())) { - httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - return; - } - - chain.doFilter(httpRequest, httpResponse); - - // Clickjacking protection - // See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE - httpResponse.addHeader("X-Frame-Options", "SAMEORIGIN"); - - // Cross-site scripting - // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers - httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); - - // MIME-sniffing - // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers - httpResponse.addHeader("X-Content-Type-Options", "nosniff"); - } - - @Override - public void destroy() { - // nothing - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java new file mode 100644 index 00000000000..8fe21c22436 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java @@ -0,0 +1,87 @@ +/* + * 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.platform; + +import java.io.File; + +/** + * Replaces the incomplete {@link org.sonar.api.platform.ServerFileSystem} as many directories can't be + * published in API. + * + * @since 6.0 + */ +public interface ServerFileSystem { + + /** + * Directory that contains the persisted data to be kept on restarts and upgrades. + * @return an existing directory + */ + File getDataDir(); + + /** + * Directory accessible by scanners through web server + * @return a non-null directory that MAY exist + */ + File getDeployDir(); + + /** + * Root directory of the server installation + * @return an existing directory + */ + File getHomeDir(); + + /** + * Temporary directory, clean up on restarts + * @return an existing directory + */ + File getTempDir(); + + /** + * Files of plugins published by web server for scanners + * @return a non-null directory that MAY exist + */ + File getDeployedPluginsDir(); + + /** + * Directory of plugins downloaded through update center. Files + * will be moved to {@link #getInstalledPluginsDir()} on startup. + * @return an existing directory + */ + File getDownloadedPluginsDir(); + + /** + * Directory of currently installed plugins. Used at startup. + * @return an existing directory + */ + File getInstalledPluginsDir(); + + /** + * Directory of the plugins packaged by default with application. These + * plugins are installed during the first fresh startup. + * @return an existing directory + */ + File getBundledPluginsDir(); + + /** + * The file listing all the installed plugins. Used by scanner only. + * @return an existing file + */ + File getPluginIndex(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java new file mode 100644 index 00000000000..258568d1035 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java @@ -0,0 +1,103 @@ +/* + * 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.platform; + +import java.io.File; +import org.picocontainer.Startable; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.process.ProcessProperties; +import org.sonar.server.app.TomcatContexts; + +import static java.util.Objects.requireNonNull; + +public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.platform.ServerFileSystem, Startable { + + private static final Logger LOGGER = Loggers.get(ServerFileSystemImpl.class); + + private final File homeDir; + private final File tempDir; + private final File dataDir; + private final File deployDir; + + public ServerFileSystemImpl(Settings settings) { + this.homeDir = new File(requireNonNull(settings.getString(ProcessProperties.PATH_HOME))); + this.tempDir = new File(requireNonNull(settings.getString(ProcessProperties.PATH_TEMP))); + this.dataDir = new File(requireNonNull(settings.getString(ProcessProperties.PATH_DATA))); + this.deployDir = new File(this.dataDir, TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR); + } + + @Override + public void start() { + LOGGER.info("SonarQube home: " + homeDir.getAbsolutePath()); + } + + @Override + public void stop() { + // do nothing + } + + @Override + public File getHomeDir() { + return homeDir; + } + + @Override + public File getTempDir() { + return tempDir; + } + + @Override + public File getDataDir() { + return dataDir; + } + + @Override + public File getDeployDir() { + return deployDir; + } + + @Override + public File getDeployedPluginsDir() { + return new File(getDeployDir(), "plugins"); + } + + @Override + public File getDownloadedPluginsDir() { + return new File(getHomeDir(), "extensions/downloads"); + } + + @Override + public File getInstalledPluginsDir() { + return new File(getHomeDir(), "extensions/plugins"); + } + + @Override + public File getBundledPluginsDir() { + return new File(getHomeDir(), "lib/bundled-plugins"); + } + + @Override + public File getPluginIndex() { + return new File(getDeployDir(), "plugins/index.txt"); + } + +} 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 a3a834077c5..e9a9e714115 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 @@ -40,7 +40,6 @@ 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.TomcatContexts; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -65,7 +64,6 @@ public final class ServerImpl extends Server implements Startable { private String version; private String implementationBuild; private File sonarHome; - private File deployDir; private String contextPath; public ServerImpl(Settings settings) { @@ -94,8 +92,6 @@ 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), TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR); - contextPath = StringUtils.defaultIfBlank(settings.getString(PROPERTY_CONTEXT), "") // Remove trailing slashes .replaceFirst("(\\/+)$", ""); @@ -144,7 +140,7 @@ public final class ServerImpl extends Server implements Startable { @Override @CheckForNull public File getDeployDir() { - return deployDir; + return null; } @Override diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/TempFolderProvider.java b/server/sonar-server/src/main/java/org/sonar/server/platform/TempFolderProvider.java index 1927f95f51f..7910ed57eb6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/TempFolderProvider.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/TempFolderProvider.java @@ -19,15 +19,13 @@ */ package org.sonar.server.platform; +import java.io.File; +import java.io.IOException; import org.apache.commons.io.FileUtils; import org.picocontainer.injectors.ProviderAdapter; -import org.sonar.api.platform.ServerFileSystem; import org.sonar.api.utils.TempFolder; import org.sonar.api.utils.internal.DefaultTempFolder; -import java.io.File; -import java.io.IOException; - public class TempFolderProvider extends ProviderAdapter { private TempFolder tempFolder; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java index 4be5c21f58b..3b44379c826 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/cluster/ClusterProperties.java @@ -1,3 +1,22 @@ +/* + * 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.platform.cluster; import com.google.common.collect.ImmutableList; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java index 58053ce3498..54bcf9d83e9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java @@ -43,7 +43,7 @@ import org.sonar.server.app.RestartFlagHolderImpl; import org.sonar.server.db.EmbeddedDatabaseFactory; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.platform.DatabaseServerCompatibility; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystemImpl; import org.sonar.server.platform.Platform; import org.sonar.server.platform.ServerImpl; import org.sonar.server.platform.ServerSettingsImpl; @@ -89,7 +89,7 @@ public class PlatformLevel1 extends PlatformLevel { DatabaseServerCompatibility.class, DatabaseVersion.class, PurgeProfiler.class, - DefaultServerFileSystem.class, + ServerFileSystemImpl.class, SemaphoresImpl.class, TempFolderCleaner.class, new TempFolderProvider(), diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java index 996aa970dbe..4c1455e2f07 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java @@ -31,7 +31,7 @@ import org.sonar.server.db.migrations.DatabaseMigrator; import org.sonar.server.db.migrations.PlatformDatabaseMigration; import org.sonar.server.db.migrations.PlatformDatabaseMigrationExecutorServiceImpl; import org.sonar.server.platform.DefaultServerUpgradeStatus; -import org.sonar.server.platform.RailsAppsDeployer; +import org.sonar.server.platform.web.RailsAppsDeployer; import org.sonar.server.plugins.InstalledPluginReferentialFactory; import org.sonar.server.plugins.ServerPluginJarExploder; import org.sonar.server.plugins.ServerPluginRepository; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index ae8a6c8ee43..7ba5aec89e1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -36,7 +36,7 @@ import org.sonar.server.startup.RegisterDashboards; import org.sonar.server.startup.RegisterMetrics; import org.sonar.server.startup.RegisterNewMeasureFilters; import org.sonar.server.startup.RegisterPermissionTemplates; -import org.sonar.server.startup.RegisterServletFilters; +import org.sonar.server.platform.web.RegisterServletFilters; import org.sonar.server.startup.RenameDeprecatedPropertyKeys; import org.sonar.server.startup.RenameIssueWidgets; import org.sonar.server.user.DoPrivileged; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java new file mode 100644 index 00000000000..76f3abd0822 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/MasterServletFilter.java @@ -0,0 +1,137 @@ +/* + * 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.platform.web; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.web.ServletFilter; +import org.sonar.server.platform.Platform; + +/** + * Inspired by http://stackoverflow.com/a/7592883/229031 + */ +public class MasterServletFilter implements Filter { + + public static volatile MasterServletFilter INSTANCE; + private ServletFilter[] filters; + private FilterConfig config; + + public MasterServletFilter() { + if (INSTANCE != null) { + throw new IllegalStateException("Servlet filter " + getClass().getName() + " is already instantiated"); + } + INSTANCE = this; + } + + @Override + public void init(FilterConfig config) throws ServletException { + // Filters are already available in picocontainer unless a database migration is required. See + // org.sonar.server.startup.RegisterServletFilters. + init(config, Platform.getInstance().getContainer().getComponentsByType(ServletFilter.class)); + } + + void init(FilterConfig config, List filters) throws ServletException { + this.config = config; + initFilters(filters); + } + + public void initFilters(List filterExtensions) throws ServletException { + List filterList = Lists.newArrayList(); + for (ServletFilter extension : filterExtensions) { + try { + Loggers.get(MasterServletFilter.class).info(String.format("Initializing servlet filter %s [pattern=%s]", extension, extension.doGetPattern())); + extension.init(config); + filterList.add(extension); + } catch (Exception e) { + throw new IllegalStateException("Fail to initialize servlet filter: " + extension + ". Message: " + e.getMessage(), e); + } + } + filters = filterList.toArray(new ServletFilter[filterList.size()]); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { + HttpServletRequest hsr = (HttpServletRequest) request; + if (filters.length == 0) { + chain.doFilter(request, response); + } else { + String path = hsr.getRequestURI().replaceFirst(hsr.getContextPath(), ""); + GodFilterChain godChain = new GodFilterChain(chain); + + for (ServletFilter filter : filters) { + if (filter.doGetPattern().matches(path)) { + godChain.addFilter(filter); + } + } + godChain.doFilter(request, response); + } + } + + @Override + public void destroy() { + for (ServletFilter filter : filters) { + filter.destroy(); + } + } + + @VisibleForTesting + ServletFilter[] getFilters() { + return filters; + } + + private static final class GodFilterChain implements FilterChain { + private FilterChain chain; + private List filters = Lists.newLinkedList(); + private Iterator iterator; + + public GodFilterChain(FilterChain chain) { + this.chain = chain; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + if (iterator == null) { + iterator = filters.iterator(); + } + if (iterator.hasNext()) { + iterator.next().doFilter(request, response, this); + } else { + chain.doFilter(request, response); + } + } + + public void addFilter(Filter filter) { + Preconditions.checkState(iterator == null); + filters.add(filter); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java new file mode 100644 index 00000000000..d83304190e7 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java @@ -0,0 +1,71 @@ +/* + * 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.platform.web; + +import com.google.common.base.Throwables; +import java.util.Enumeration; +import java.util.Properties; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import org.sonar.server.platform.Platform; + +public final class PlatformServletContextListener implements ServletContextListener { + + static final String STARTED_ATTRIBUTE = "sonarqube.started"; + + @Override + public void contextInitialized(ServletContextEvent event) { + try { + Properties props = new Properties(); + ServletContext servletContext = event.getServletContext(); + Enumeration paramKeys = servletContext.getInitParameterNames(); + while (paramKeys.hasMoreElements()) { + String key = paramKeys.nextElement(); + props.put(key, servletContext.getInitParameter(key)); + } + Platform.getInstance().init(props, servletContext); + Platform.getInstance().doStart(); + event.getServletContext().setAttribute(STARTED_ATTRIBUTE, Boolean.TRUE); + + } catch (Throwable t) { + // Tomcat 7 "limitations": + // - server does not stop if webapp fails at startup + // - the second listener for jruby on rails is started even if this listener fails. It generates + // unexpected errors + stopQuietly(); + throw Throwables.propagate(t); + } + } + + private void stopQuietly() { + try { + Platform.getInstance().doStop(); + } catch (Exception e) { + // ignored, but an error during startup generally prevents pico to be correctly stopped + } + } + + @Override + public void contextDestroyed(ServletContextEvent event) { + Platform.getInstance().doStop(); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java new file mode 100644 index 00000000000..8728a7b401c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/ProfilingFilter.java @@ -0,0 +1,115 @@ +/* + * 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.platform.web; + +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.log.Loggers; +import org.sonar.api.utils.log.Profiler; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import java.io.IOException; +import java.util.Set; + +/** + *

Profile HTTP requests using platform profiling utility.

+ *

To avoid profiling of requests for static resources, the staticDirs + * filter parameter can be set in the servlet context descriptor. This parameter should + * contain a comma-separated list of paths, starting at the context root; + * requests on subpaths of these paths will not be profiled.

+ * + * @since 4.1 + */ +public class ProfilingFilter implements Filter { + + private static final String CONFIG_SEPARATOR = ","; + private static final String URL_SEPARATOR = "/"; + + private static final String MESSAGE_WITH_QUERY = "%s %s?%s"; + private static final String MESSAGE_WITHOUT_QUERY = "%s %s"; + public static final org.sonar.api.utils.log.Logger Logger = Loggers.get("http"); + + private String contextRoot; + private Set staticResourceDirs; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + contextRoot = filterConfig.getServletContext().getContextPath(); + + String staticResourcesConfig = filterConfig.getInitParameter("staticDirs"); + if (StringUtils.isNotBlank(staticResourcesConfig)) { + staticResourceDirs = ImmutableSet.copyOf(staticResourcesConfig.split(CONFIG_SEPARATOR)); + } else { + staticResourceDirs = ImmutableSet.of(); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String requestUri = httpRequest.getRequestURI(); + String rootDir = getRootDir(requestUri); + + if (staticResourceDirs.contains(rootDir)) { + // Static resource, not profiled + chain.doFilter(request, response); + } else { + Profiler profiler = Profiler.createIfDebug(Logger).start(); + try { + chain.doFilter(request, response); + } finally { + if (profiler.isDebugEnabled()) { + String queryString = httpRequest.getQueryString(); + String message = String.format(queryString == null ? MESSAGE_WITHOUT_QUERY : MESSAGE_WITH_QUERY, httpRequest.getMethod(), requestUri, queryString); + profiler.stopDebug(message); + } + } + } + } else { + // Not an HTTP request, not profiled + chain.doFilter(request, response); + } + } + + private String getRootDir(String requestUri) { + String rootPath = ""; + String localPath = StringUtils.substringAfter(requestUri, contextRoot); + if (localPath.startsWith(URL_SEPARATOR)) { + int secondSlash = localPath.indexOf(URL_SEPARATOR, 1); + if (secondSlash > 0) { + rootPath = URL_SEPARATOR + localPath.substring(1, secondSlash); + } + } + return rootPath; + } + + @Override + public void destroy() { + // Nothing + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java new file mode 100644 index 00000000000..9de419f2bc0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RailsAppsDeployer.java @@ -0,0 +1,121 @@ +/* + * 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.platform.web; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; +import org.sonar.api.Plugin; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; +import org.sonar.server.platform.ServerFileSystem; +import org.sonar.server.util.ClassLoaderUtils; + +/** + * Ruby on Rails requires the files to be on filesystem but not in Java classpath (JAR). This component extracts + * all the needed files from plugins and copy them to $SONAR_HOME/temp + * + * @since 3.0 + */ +public class RailsAppsDeployer implements Startable { + + private static final Logger LOG = Loggers.get(RailsAppsDeployer.class); + private static final String ROR_PATH = "org/sonar/ror/"; + + private final ServerFileSystem fs; + private final PluginRepository pluginRepository; + + public RailsAppsDeployer(ServerFileSystem fs, PluginRepository pluginRepository) { + this.fs = fs; + this.pluginRepository = pluginRepository; + } + + @Override + public void start() { + LOG.info("Deploying Ruby on Rails applications"); + File appsDir = prepareRailsDirectory(); + + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + String pluginKey = pluginInfo.getKey(); + Plugin plugin = pluginRepository.getPluginInstance(pluginKey); + try { + deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader()); + } catch (Exception e) { + throw new IllegalStateException(String.format("Fail to deploy Ruby on Rails application of plugin [%s]", pluginKey), e); + } + } + } + + @Override + public void stop() { + // do nothing + } + + @VisibleForTesting + File prepareRailsDirectory() { + File appsDir = new File(fs.getTempDir(), "ror"); + prepareDir(appsDir); + return appsDir; + } + + @VisibleForTesting + static void deployRailsApp(File appsDir, final String pluginKey, ClassLoader appClassLoader) { + if (hasRailsApp(pluginKey, appClassLoader)) { + LOG.info("Deploying app: " + pluginKey); + File appDir = new File(appsDir, pluginKey); + ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, relativePath -> { + // Relocate the deployed files : + // relativePath format is: org/sonar/ror/sqale/app/controllers/foo_controller.rb + // app path is: org/sonar/ror/sqale + // -> deployed file is app/controllers/foo_controller.rb + return StringUtils.substringAfter(relativePath, pluginKey + "/"); + }); + } + } + + private static String pathToRubyInitFile(String pluginKey) { + return ROR_PATH + pluginKey + "/init.rb"; + } + + @VisibleForTesting + static boolean hasRailsApp(String pluginKey, ClassLoader classLoader) { + return classLoader.getResource(pathToRubyInitFile(pluginKey)) != null; + } + + private void prepareDir(File appsDir) { + if (appsDir.exists() && appsDir.isDirectory()) { + try { + org.sonar.core.util.FileUtils.deleteDirectory(appsDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to delete temp directory: " + appsDir, e); + } + } + try { + FileUtils.forceMkdir(appsDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create temp directory: " + appsDir, e); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java new file mode 100644 index 00000000000..3b0dba41233 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RegisterServletFilters.java @@ -0,0 +1,49 @@ +/* + * 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.platform.web; + +import java.util.Arrays; +import javax.servlet.ServletException; +import org.sonar.api.web.ServletFilter; + +/** + * @since 3.5 + */ +public class RegisterServletFilters { + private final ServletFilter[] filters; + + public RegisterServletFilters(ServletFilter[] filters) { + this.filters = filters; + } + + public RegisterServletFilters() { + this(new ServletFilter[0]); + } + + public void start() throws ServletException { + if (MasterServletFilter.INSTANCE != null) { + // Probably a database upgrade. MasterSlaveFilter was instantiated by the servlet container + // while picocontainer was not completely up. + // See https://jira.sonarsource.com/browse/SONAR-3612 + MasterServletFilter.INSTANCE.initFilters(Arrays.asList(filters)); + } + } +} + diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RoutesFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RoutesFilter.java new file mode 100644 index 00000000000..5de1bd2971b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RoutesFilter.java @@ -0,0 +1,68 @@ +/* + * 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.platform.web; + +import static java.lang.String.format; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RoutesFilter implements Filter { + + private static final String EMPTY = ""; + private static final String BATCH_WS = "/batch"; + private static final String API_SOURCES_WS = "/api/sources"; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + String path = request.getRequestURI().replaceFirst(request.getContextPath(), EMPTY); + if (path.startsWith(BATCH_WS + "/") && path.endsWith(".jar")) { + // Scanner is still using /batch/file.jar url + response.sendRedirect(format("%s%s/file?name=%s", request.getContextPath(), BATCH_WS, path.replace(BATCH_WS + "/", EMPTY))); + } else if ("/batch_bootstrap/index".equals(path)) { + // Scanner is still using /batch_bootstrap url + response.sendRedirect(format("%s%s/index", request.getContextPath(), BATCH_WS)); + } else if (API_SOURCES_WS.equals(path)) { + // SONAR-7852 /api/sources?resource url is still used + response.sendRedirect(format("%s%s/index?%s", request.getContextPath(), API_SOURCES_WS, request.getQueryString())); + } else { + chain.doFilter(request, response); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Nothing + } + + @Override + public void destroy() { + // Nothing + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/RubyRailsContextListener.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RubyRailsContextListener.java new file mode 100644 index 00000000000..bdc957b4269 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/RubyRailsContextListener.java @@ -0,0 +1,51 @@ +/* + * 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.platform.web; + +import com.google.common.base.Throwables; +import javax.servlet.ServletContextEvent; +import org.jruby.rack.RackApplicationFactory; +import org.jruby.rack.rails.RailsServletContextListener; +import org.jruby.rack.servlet.ServletRackContext; + +/** + * Overriding {@link RailsServletContextListener} allows to disable initialization of Ruby on Rails + * environment when Java components fail to start. + * See https://jira.sonarsource.com/browse/SONAR-6740 + */ +public class RubyRailsContextListener extends RailsServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent event) { + if (event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE) != null) { + super.contextInitialized(event); + } + } + + // Always stop server when an error is raised during startup. + // By default Rack only logs an error (see org.jruby.rack.RackServletContextListener#handleInitializationException()). + // Rack propagates exceptions if the properties jruby.rack.exception or jruby.rack.error are set to true. + // Unfortunately we didn't succeed in defining these properties, so the method is overridden here. + // Initial need: SONAR-6171 + @Override + protected void handleInitializationException(Exception e, RackApplicationFactory factory, ServletRackContext rackContext) { + throw Throwables.propagate(e); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/SecurityServletFilter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/SecurityServletFilter.java new file mode 100644 index 00000000000..a91d29e0262 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/SecurityServletFilter.java @@ -0,0 +1,78 @@ +/* + * 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.platform.web; + +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.util.Set; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * This servlet filter sets response headers that enable browser protection against several classes if Web attacks. + * The list of headers is mirrored in environment.rb as a workaround to Rack swallowing the headers.. + */ +public class SecurityServletFilter implements Filter { + + private static final Set ALLOWED_HTTP_METHODS = ImmutableSet.of("DELETE", "GET", "HEAD", "POST", "PUT"); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // nothing + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { + doHttpFilter((HttpServletRequest) req, (HttpServletResponse) resp, chain); + } + + private static void doHttpFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException { + // SONAR-6881 Disable OPTIONS and TRACE methods + if (!ALLOWED_HTTP_METHODS.contains(httpRequest.getMethod())) { + httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + chain.doFilter(httpRequest, httpResponse); + + // Clickjacking protection + // See https://www.owasp.org/index.php/Clickjacking_Protection_for_Java_EE + httpResponse.addHeader("X-Frame-Options", "SAMEORIGIN"); + + // Cross-site scripting + // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); + + // MIME-sniffing + // See https://www.owasp.org/index.php/List_of_useful_HTTP_headers + httpResponse.addHeader("X-Content-Type-Options", "nosniff"); + } + + @Override + public void destroy() { + // nothing + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/web/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/platform/web/package-info.java new file mode 100644 index 00000000000..5bdbd56dcd9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/web/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.platform.web; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java index 436e657efa4..0b400478ad0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java @@ -35,7 +35,7 @@ import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.PluginInfo; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.UpdateCenter; import org.sonar.updatecenter.common.Version; @@ -65,7 +65,7 @@ public class PluginDownloader implements Startable { private final File downloadDir; public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, - DefaultServerFileSystem fileSystem) { + ServerFileSystem fileSystem) { this.updateCenterMatrixFactory = updateCenterMatrixFactory; this.downloader = downloader; this.downloadDir = fileSystem.getDownloadedPluginsDir(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java index 8f6afbea373..5b630e26b55 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java @@ -27,7 +27,7 @@ import org.sonar.api.utils.ZipUtils; import org.sonar.core.platform.ExplodedPlugin; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginJarExploder; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import static org.apache.commons.io.FileUtils.forceMkdir; @@ -35,9 +35,9 @@ import static org.apache.commons.io.FileUtils.forceMkdir; @ComputeEngineSide public class ServerPluginJarExploder extends PluginJarExploder { - private final DefaultServerFileSystem fs; + private final ServerFileSystem fs; - public ServerPluginJarExploder(DefaultServerFileSystem fs) { + public ServerPluginJarExploder(ServerFileSystem fs) { this.fs = fs; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 341bdc99837..47e3bc584e0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -48,7 +48,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginLoader; import org.sonar.core.platform.PluginRepository; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Version; import static com.google.common.base.Preconditions.checkArgument; @@ -84,7 +84,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { private static final String NOT_STARTED_YET = "not started yet"; private final Server server; - private final DefaultServerFileSystem fs; + private final ServerFileSystem fs; private final ServerUpgradeStatus upgradeStatus; private final PluginLoader loader; private final AtomicBoolean started = new AtomicBoolean(false); @@ -95,7 +95,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { private final Map pluginInstancesByKeys = new HashMap<>(); public ServerPluginRepository(Server server, ServerUpgradeStatus upgradeStatus, - DefaultServerFileSystem fs, PluginLoader loader) { + ServerFileSystem fs, PluginLoader loader) { this.server = server; this.upgradeStatus = upgradeStatus; this.fs = fs; diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java index 60537ad39e9..3e13f0472fb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java @@ -19,6 +19,12 @@ */ package org.sonar.server.startup; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.CharUtils; @@ -26,22 +32,15 @@ import org.sonar.api.server.ServerSide; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; import org.sonar.core.platform.RemotePlugin; -import org.sonar.server.platform.DefaultServerFileSystem; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; +import org.sonar.server.platform.ServerFileSystem; @ServerSide public final class GeneratePluginIndex { - private DefaultServerFileSystem fileSystem; - private PluginRepository repository; + private final ServerFileSystem fileSystem; + private final PluginRepository repository; - public GeneratePluginIndex(DefaultServerFileSystem fileSystem, PluginRepository repository) { + public GeneratePluginIndex(ServerFileSystem fileSystem, PluginRepository repository) { this.fileSystem = fileSystem; this.repository = repository; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterServletFilters.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterServletFilters.java deleted file mode 100644 index 160adfaf6f8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterServletFilters.java +++ /dev/null @@ -1,52 +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.startup; - -import org.sonar.api.web.ServletFilter; -import org.sonar.server.platform.MasterServletFilter; - -import javax.servlet.ServletException; - -import java.util.Arrays; - -/** - * @since 3.5 - */ -public class RegisterServletFilters { - private final ServletFilter[] filters; - - public RegisterServletFilters(ServletFilter[] filters) { - this.filters = filters; - } - - public RegisterServletFilters() { - this(new ServletFilter[0]); - } - - public void start() throws ServletException { - if (MasterServletFilter.INSTANCE != null) { - // Probably a database upgrade. MasterSlaveFilter was instantiated by the servlet container - // while picocontainer was not completely up. - // See https://jira.sonarsource.com/browse/SONAR-3612 - MasterServletFilter.INSTANCE.initFilters(Arrays.asList(filters)); - } - } -} - diff --git a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java index 7bdcb3f5d61..da4cce1c24a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java @@ -20,11 +20,6 @@ package org.sonar.server.updatecenter.ws; -import static com.google.common.base.Preconditions.checkArgument; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static org.apache.commons.io.IOUtils.closeQuietly; -import static org.sonar.api.server.ws.Request.Part; - import java.io.File; import java.io.InputStream; import java.nio.file.Files; @@ -32,9 +27,14 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.server.user.UserSession; +import static com.google.common.base.Preconditions.checkArgument; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.apache.commons.io.IOUtils.closeQuietly; +import static org.sonar.api.server.ws.Request.Part; + public class UploadAction implements UpdateCenterWsAction { public static final String PARAM_FILE = "file"; @@ -42,7 +42,7 @@ public class UploadAction implements UpdateCenterWsAction { private final UserSession userSession; private final File downloadDir; - public UploadAction(UserSession userSession, DefaultServerFileSystem fileSystem) { + public UploadAction(UserSession userSession, ServerFileSystem fileSystem) { this.userSession = userSession; this.downloadDir = fileSystem.getDownloadedPluginsDir(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java b/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java new file mode 100644 index 00000000000..68b5bb882a1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/util/ClassLoaderUtils.java @@ -0,0 +1,136 @@ +/* + * 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.util; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Collection; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.CharEncoding; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.log.Loggers; + +/** + * @since 3.0 + */ +public class ClassLoaderUtils { + + private ClassLoaderUtils() { + // only static methods + } + + public static File copyResources(ClassLoader classLoader, String rootPath, File toDir, Function relocationFunction) { + Collection relativePaths = listFiles(classLoader, rootPath); + for (String relativePath : relativePaths) { + URL resource = classLoader.getResource(relativePath); + String filename = relocationFunction.apply(relativePath); + File toFile = new File(toDir, filename); + try { + FileUtils.copyURLToFile(resource, toFile); + } catch (IOException e) { + throw new IllegalStateException("Fail to extract " + relativePath + " to " + toFile.getAbsolutePath(), e); + } + } + + return toDir; + } + + /** + * Finds files within a given directory and its subdirectories + * + * @param classLoader + * @param rootPath the root directory, for example org/sonar/sqale + * @return a list of relative paths, for example {"org/sonar/sqale/foo/bar.txt}. Never null. + */ + public static Collection listFiles(ClassLoader classLoader, String rootPath) { + return listResources(classLoader, rootPath, path -> !StringUtils.endsWith(path, "/")); + } + + /** + * Finds directories and files within a given directory and its subdirectories. + * + * @param classLoader + * @param rootPath the root directory, for example org/sonar/sqale, or a file in this root directory, for example org/sonar/sqale/index.txt + * @param predicate + * @return a list of relative paths, for example {"org/sonar/sqale", "org/sonar/sqale/foo", "org/sonar/sqale/foo/bar.txt}. Never null. + */ + public static Collection listResources(ClassLoader classLoader, String rootPath, Predicate predicate) { + String jarPath = null; + JarFile jar = null; + try { + Collection paths = Lists.newArrayList(); + URL root = classLoader.getResource(rootPath); + if (root != null) { + checkJarFile(root); + + // Path of the root directory + // Examples : + // org/sonar/sqale/index.txt -> rootDirectory is org/sonar/sqale + // org/sonar/sqale/ -> rootDirectory is org/sonar/sqale + // org/sonar/sqale -> rootDirectory is org/sonar/sqale + String rootDirectory = rootPath; + if (StringUtils.substringAfterLast(rootPath, "/").indexOf('.') >= 0) { + rootDirectory = StringUtils.substringBeforeLast(rootPath, "/"); + } + //strip out only the JAR file + jarPath = root.getPath().substring(5, root.getPath().indexOf('!')); + jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8)); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + String name = entries.nextElement().getName(); + if (name.startsWith(rootDirectory) && predicate.apply(name)) { + paths.add(name); + } + } + } + return paths; + } catch (Exception e) { + throw Throwables.propagate(e); + } finally { + closeJar(jar, jarPath); + } + } + + private static void closeJar(@Nullable JarFile jar, String jarPath) { + if (jar != null) { + try { + jar.close(); + } catch (Exception e) { + Loggers.get(ClassLoaderUtils.class).error("Fail to close JAR file: " + jarPath, e); + } + } + } + + private static void checkJarFile(URL root) { + if (!"jar".equals(root.getProtocol())) { + throw new IllegalStateException("Unsupported protocol: " + root.getProtocol()); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchIndexTest.java index 7baaf99538b..9f73068a1c5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/BatchIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/BatchIndexTest.java @@ -19,6 +19,8 @@ */ package org.sonar.server.batch; +import java.io.File; +import java.io.IOException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.CharUtils; import org.junit.Before; @@ -26,10 +28,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.Server; - -import java.io.File; -import java.io.IOException; +import org.sonar.server.platform.ServerFileSystem; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -43,16 +42,16 @@ public class BatchIndexTest { @Rule public ExpectedException thrown = ExpectedException.none(); - File jar; + private File jar; - Server server = mock(Server.class); + private ServerFileSystem fs = mock(ServerFileSystem.class); @Before public void prepare_fs() throws IOException { - File rootDir = temp.newFolder(); - when(server.getRootDir()).thenReturn(rootDir); + File homeDir = temp.newFolder(); + when(fs.getHomeDir()).thenReturn(homeDir); - File batchDir = new File(rootDir, "lib/batch"); + File batchDir = new File(homeDir, "lib/batch"); FileUtils.forceMkdir(batchDir); jar = new File(batchDir, "sonar-batch.jar"); FileUtils.writeStringToFile(new File(batchDir, "sonar-batch.jar"), "foo"); @@ -60,7 +59,7 @@ public class BatchIndexTest { @Test public void get_index() { - BatchIndex batchIndex = new BatchIndex(server); + BatchIndex batchIndex = new BatchIndex(fs); batchIndex.start(); String index = batchIndex.getIndex(); @@ -71,7 +70,7 @@ public class BatchIndexTest { @Test public void get_file() { - BatchIndex batchIndex = new BatchIndex(server); + BatchIndex batchIndex = new BatchIndex(fs); batchIndex.start(); File file = batchIndex.getFile("sonar-batch.jar"); @@ -87,7 +86,7 @@ public class BatchIndexTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Bad filename: ../sonar-batch.jar"); - BatchIndex batchIndex = new BatchIndex(server); + BatchIndex batchIndex = new BatchIndex(fs); batchIndex.start(); batchIndex.getFile("../sonar-batch.jar"); @@ -98,7 +97,7 @@ public class BatchIndexTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Bad filename: other.jar"); - BatchIndex batchIndex = new BatchIndex(server); + BatchIndex batchIndex = new BatchIndex(fs); batchIndex.start(); batchIndex.getFile("other.jar"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java index 3b957fddc2f..66f2191f6c3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java @@ -27,7 +27,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.config.Settings; -import org.sonar.api.platform.Server; import org.sonar.api.security.DefaultGroups; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; @@ -46,6 +45,7 @@ import org.sonar.server.issue.index.IssueDoc; import org.sonar.server.issue.index.IssueIndex; import org.sonar.server.issue.index.IssueIndexDefinition; import org.sonar.server.issue.index.IssueIndexer; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; @@ -76,9 +76,10 @@ public class IssuesActionTest { @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); - IssueIndex issueIndex; - IssueIndexer issueIndexer; - IssueAuthorizationIndexer issueAuthorizationIndexer; + private IssueIndex issueIndex; + private IssueIndexer issueIndexer; + private IssueAuthorizationIndexer issueAuthorizationIndexer; + private ServerFileSystem fs = mock(ServerFileSystem.class); WsTester tester; @@ -91,7 +92,7 @@ public class IssuesActionTest { issueAuthorizationIndexer = new IssueAuthorizationIndexer(null, es.client()); issuesAction = new IssuesAction(db.getDbClient(), issueIndex, userSessionRule, new ComponentFinder(db.getDbClient())); - tester = new WsTester(new BatchWs(new BatchIndex(mock(Server.class)), issuesAction)); + tester = new WsTester(new BatchWs(new BatchIndex(fs), issuesAction)); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/UsersActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/UsersActionTest.java index f877b0e9d69..30a96caffe2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/UsersActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/UsersActionTest.java @@ -27,10 +27,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.config.Settings; -import org.sonar.api.platform.Server; import org.sonar.scanner.protocol.input.ScannerInput.User; import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.user.index.UserDoc; import org.sonar.server.user.index.UserIndex; @@ -61,7 +61,7 @@ public class UsersActionTest { public void before() { userIndex = new UserIndex(es.client()); usersAction = new UsersAction(userIndex, userSessionRule); - tester = new WsTester(new BatchWs(new BatchIndex(mock(Server.class)), usersAction)); + tester = new WsTester(new BatchWs(new BatchIndex(mock(ServerFileSystem.class)), usersAction)); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java index af1e1093aff..c19c7274ace 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ClassLoaderUtilsTest.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; +import org.sonar.server.util.ClassLoaderUtils; import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.hasItems; diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/MasterServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/MasterServletFilterTest.java deleted file mode 100644 index 157879fe489..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/MasterServletFilterTest.java +++ /dev/null @@ -1,143 +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.platform; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.web.ServletFilter; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; - -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; -import static org.mockito.Mockito.when; - -public class MasterServletFilterTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void resetSingleton() { - MasterServletFilter.INSTANCE = null; - } - - @Test - public void should_init_and_destroy_filters() throws Exception { - ServletFilter filter = mock(ServletFilter.class); - FilterConfig config = mock(FilterConfig.class); - MasterServletFilter master = new MasterServletFilter(); - master.init(config, Arrays.asList(filter)); - - assertThat(master.getFilters()).containsOnly(filter); - verify(filter).init(config); - - master.destroy(); - verify(filter).destroy(); - } - - @Test - public void servlet_container_should_instantiate_only_a_single_master_instance() { - new MasterServletFilter(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Servlet filter org.sonar.server.platform.MasterServletFilter is already instantiated"); - new MasterServletFilter(); - } - - @Test - public void should_propagate_initialization_failure() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("foo"); - - ServletFilter filter = mock(ServletFilter.class); - doThrow(new IllegalStateException("foo")).when(filter).init(any(FilterConfig.class)); - - FilterConfig config = mock(FilterConfig.class); - MasterServletFilter filters = new MasterServletFilter(); - filters.init(config, Arrays.asList(filter)); - } - - @Test - public void filters_should_be_optional() throws Exception { - FilterConfig config = mock(FilterConfig.class); - MasterServletFilter filters = new MasterServletFilter(); - filters.init(config, Collections.emptyList()); - - ServletRequest request = mock(HttpServletRequest.class); - ServletResponse response = mock(HttpServletResponse.class); - FilterChain chain = mock(FilterChain.class); - filters.doFilter(request, response, chain); - - verify(chain).doFilter(request, response); - } - - @Test - public void should_keep_filter_ordering() throws Exception { - TrueFilter filter1 = new TrueFilter(); - TrueFilter filter2 = new TrueFilter(); - - MasterServletFilter filters = new MasterServletFilter(); - filters.init(mock(FilterConfig.class), Arrays.asList(filter1, filter2)); - - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/foo/bar"); - when(request.getContextPath()).thenReturn(""); - ServletResponse response = mock(HttpServletResponse.class); - FilterChain chain = mock(FilterChain.class); - filters.doFilter(request, response, chain); - - assertThat(filter1.count).isEqualTo(1); - assertThat(filter2.count).isEqualTo(2); - } - - private static final class TrueFilter extends ServletFilter { - private static int globalCount = 0; - private int count = 0; - - public void init(FilterConfig filterConfig) throws ServletException { - } - - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { - globalCount++; - count = globalCount; - filterChain.doFilter(servletRequest, servletResponse); - } - - public void destroy() { - } - - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ProfilingFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ProfilingFilterTest.java deleted file mode 100644 index dc27813fbea..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ProfilingFilterTest.java +++ /dev/null @@ -1,105 +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.platform; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ProfilingFilterTest { - - private ProfilingFilter filter; - private FilterChain chain; - - @Before - public void initialize() throws Exception { - FilterConfig filterConfig = mock(FilterConfig.class); - when(filterConfig.getInitParameter("staticDirs")).thenReturn("/static,/assets"); - ServletContext context = mock(ServletContext.class); - when(context.getContextPath()).thenReturn("/context"); - when(filterConfig.getServletContext()).thenReturn(context); - chain = mock(FilterChain.class); - - filter = new ProfilingFilter(); - filter.init(filterConfig); - } - - @Test - public void should_profile_service_call() throws Exception { - filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); - } - - @Test - public void should_profile_service() throws Exception { - filter.doFilter(request("POST", "/context/service", null), null, chain); - } - - @Test - public void should_profile_context_root_as_slash2() throws Exception { - filter.doFilter(request("POST", "/context", null), null, chain); - } - - @Test(expected = ServletException.class) - public void should_profile_even_when_exception() throws Exception { - Mockito.doThrow(new ServletException()).when(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); - filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); - } - - @Test - public void should_not_profile_non_http_request() throws Exception { - filter.doFilter(mock(ServletRequest.class), null, chain); - } - - @Test - public void should_not_profile_static_resource() throws Exception { - filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); - } - - @Test - public void should_profile_static_resource_if_no_config() throws Exception { - FilterConfig filterConfig = mock(FilterConfig.class); - ServletContext context = mock(ServletContext.class); - when(context.getContextPath()).thenReturn("/context"); - when(filterConfig.getServletContext()).thenReturn(context); - - filter.init(filterConfig); - filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); - } - - private HttpServletRequest request(String method, String path, String query) { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getMethod()).thenReturn(method); - when(request.getRequestURI()).thenReturn(path); - when(request.getQueryString()).thenReturn(query); - return request; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java deleted file mode 100644 index a725233c28c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java +++ /dev/null @@ -1,118 +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.platform; - -import org.apache.commons.io.FileUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.ServerFileSystem; -import org.sonar.core.platform.PluginInfo; -import org.sonar.core.platform.PluginRepository; - -import java.io.File; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collections; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class RailsAppsDeployerTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @Test - public void hasRubyRailsApp() throws Exception { - ClassLoader classLoader = new URLClassLoader(new URL[]{ - getClass().getResource("/org/sonar/server/platform/RailsAppsDeployerTest/FakeRubyRailsApp.jar").toURI().toURL()}, null); - - assertTrue(RailsAppsDeployer.hasRailsApp("fake", classLoader)); - assertFalse(RailsAppsDeployer.hasRailsApp("other", classLoader)); - } - - @Test - public void deployRubyRailsApp() throws Exception { - File tempDir = this.temp.getRoot(); - ClassLoader classLoader = new URLClassLoader(new URL[]{ - getClass().getResource("/org/sonar/server/platform/RailsAppsDeployerTest/FakeRubyRailsApp.jar").toURI().toURL()}, null); - - RailsAppsDeployer.deployRailsApp(tempDir, "fake", classLoader); - - File appDir = new File(tempDir, "fake"); - assertThat(appDir.isDirectory(), is(true)); - assertThat(appDir.exists(), is(true)); - assertThat(FileUtils.listFiles(appDir, null, true).size(), is(3)); - assertThat(new File(appDir, "init.rb").exists(), is(true)); - assertThat(new File(appDir, "app/controllers/fake_controller.rb").exists(), is(true)); - assertThat(new File(appDir, "app/views/fake/index.html.erb").exists(), is(true)); - } - - @Test - public void deployRubyRailsApps_no_apps() { - ServerFileSystem fileSystem = mock(ServerFileSystem.class); - File tempDir = this.temp.getRoot(); - when(fileSystem.getTempDir()).thenReturn(tempDir); - - PluginRepository pluginRepository = mock(PluginRepository.class); - when(pluginRepository.getPluginInfos()).thenReturn(Collections.emptyList()); - new RailsAppsDeployer(fileSystem, pluginRepository).start(); - - File appDir = new File(tempDir, "ror"); - assertThat(appDir.isDirectory(), is(true)); - assertThat(appDir.exists(), is(true)); - assertThat(FileUtils.listFiles(appDir, null, true).size(), is(0)); - } - - @Test - public void prepareRubyRailsRootDirectory() throws Exception { - ServerFileSystem fileSystem = mock(ServerFileSystem.class); - File tempDir = this.temp.getRoot(); - when(fileSystem.getTempDir()).thenReturn(tempDir); - - File dir = new RailsAppsDeployer(fileSystem, mock(PluginRepository.class)).prepareRailsDirectory(); - - assertThat(dir.isDirectory(), is(true)); - assertThat(dir.exists(), is(true)); - assertThat(dir.getCanonicalPath(), is(new File(tempDir, "ror").getCanonicalPath())); - } - - @Test - public void prepareRubyRailsRootDirectory_delete_existing_dir() throws Exception { - ServerFileSystem fileSystem = mock(ServerFileSystem.class); - File tempDir = this.temp.getRoot(); - when(fileSystem.getTempDir()).thenReturn(tempDir); - - File file = new File(tempDir, "ror/foo/bar.txt"); - FileUtils.writeStringToFile(file, "foooo"); - - File dir = new RailsAppsDeployer(fileSystem, mock(PluginRepository.class)).prepareRailsDirectory(); - - assertThat(dir.isDirectory(), is(true)); - assertThat(dir.exists(), is(true)); - assertThat(dir.getCanonicalPath(), is(new File(tempDir, "ror").getCanonicalPath())); - assertThat(FileUtils.listFiles(new File(tempDir, "ror"), null, true).size(), is(0)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/RoutesFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/RoutesFilterTest.java deleted file mode 100644 index a9c4630f635..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/RoutesFilterTest.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.platform; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import javax.servlet.FilterChain; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Test; - -public class RoutesFilterTest { - - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); - FilterChain chain = mock(FilterChain.class); - - RoutesFilter underTest = new RoutesFilter(); - - @Before - public void setUp() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - } - - @Test - public void send_redirect_when_url_contains_batch_with_jar() throws Exception { - when(request.getRequestURI()).thenReturn("/batch/file.jar"); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sonarqube/batch/file?name=file.jar"); - verifyZeroInteractions(chain); - } - - @Test - public void send_redirect_when_url_contains_batch_bootstrap() throws Exception { - when(request.getRequestURI()).thenReturn("/batch_bootstrap/index"); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sonarqube/batch/index"); - verifyZeroInteractions(chain); - } - - @Test - public void send_redirect_when_url_contains_api_sources() throws Exception { - when(request.getRequestURI()).thenReturn("/api/sources"); - when(request.getQueryString()).thenReturn("resource=my.project"); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sonarqube/api/sources/index?resource=my.project"); - verifyZeroInteractions(chain); - } - - @Test - public void does_not_redirect_and_execute_remaining_filter_on_unknown_path() throws Exception { - when(request.getRequestURI()).thenReturn("/api/issues/search"); - - underTest.doFilter(request, response, chain); - - verify(chain).doFilter(request, response); - verifyZeroInteractions(response); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/RubyRailsContextListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/RubyRailsContextListenerTest.java deleted file mode 100644 index 976d7c73aac..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/RubyRailsContextListenerTest.java +++ /dev/null @@ -1,72 +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.platform; - -import java.io.IOException; -import javax.servlet.ServletContextEvent; -import org.jruby.rack.RackApplicationFactory; -import org.jruby.rack.servlet.ServletRackContext; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mockito; - -import static java.lang.Boolean.TRUE; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class RubyRailsContextListenerTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - ServletContextEvent event = mock(ServletContextEvent.class, Mockito.RETURNS_DEEP_STUBS); - RubyRailsContextListener underTest = new RubyRailsContextListener(); - - @Test - public void do_not_initialize_rails_if_error_during_startup() { - when(event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE)).thenReturn(null); - - underTest.contextInitialized(event); - - verify(event.getServletContext(), never()).setAttribute(anyString(), anyObject()); - } - - @Test - public void initialize_rails_if_no_errors_during_startup() { - when(event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE)).thenReturn(TRUE); - underTest.contextInitialized(event); - // Ruby environment is started - // See RailsServletContextListener -> RackServletContextListener - verify(event.getServletContext()).setAttribute(eq("rack.factory"), anyObject()); - } - - @Test - public void always_propagates_initialization_errors() { - expectedException.expect(RuntimeException.class); - - underTest.handleInitializationException(new IOException(), mock(RackApplicationFactory.class), mock(ServletRackContext.class)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java deleted file mode 100644 index ce61a5a77a3..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/SecurityServletFilterTest.java +++ /dev/null @@ -1,103 +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.platform; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Test; - -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.startsWith; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class SecurityServletFilterTest { - - SecurityServletFilter underTest = new SecurityServletFilter(); - HttpServletResponse response = mock(HttpServletResponse.class); - FilterChain chain = mock(FilterChain.class); - - @Test - public void allow_GET_method() throws IOException, ServletException { - assertThatMethodIsAllowed("GET"); - } - - @Test - public void allow_HEAD_method() throws IOException, ServletException { - assertThatMethodIsAllowed("HEAD"); - } - - @Test - public void allow_PUT_method() throws IOException, ServletException { - assertThatMethodIsAllowed("PUT"); - } - - @Test - public void allow_POST_method() throws IOException, ServletException { - assertThatMethodIsAllowed("POST"); - } - - private void assertThatMethodIsAllowed(String httpMethod) throws IOException, ServletException { - HttpServletRequest request = newRequest(httpMethod); - underTest.doFilter(request, response, chain); - verify(response, never()).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - verify(chain).doFilter(request, response); - } - - @Test - public void deny_OPTIONS_method() throws IOException, ServletException { - assertThatMethodIsDenied("OPTIONS"); - } - - @Test - public void deny_TRACE_method() throws IOException, ServletException { - assertThatMethodIsDenied("TRACE"); - } - - private void assertThatMethodIsDenied(String httpMethod) throws IOException, ServletException { - underTest.doFilter(newRequest(httpMethod), response, chain); - verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - } - - @Test - public void set_secured_headers() throws ServletException, IOException { - underTest.init(mock(FilterConfig.class)); - HttpServletRequest request = newRequest("GET"); - - underTest.doFilter(request, response, chain); - - verify(response, times(3)).addHeader(startsWith("X-"), anyString()); - - underTest.destroy(); - } - - private HttpServletRequest newRequest(String httpMethod) { - HttpServletRequest req = mock(HttpServletRequest.class); - when(req.getMethod()).thenReturn(httpMethod); - return req; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/TempFolderProviderTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/TempFolderProviderTest.java index eb9777bd0c7..2abcb52ae32 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/TempFolderProviderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/TempFolderProviderTest.java @@ -19,16 +19,14 @@ */ package org.sonar.server.platform; +import java.io.File; 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.api.platform.ServerFileSystem; import org.sonar.api.utils.TempFolder; -import java.io.File; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java index b4a6a69ce7a..a0378c3a452 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterImplTest.java @@ -1,3 +1,22 @@ +/* + * 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.platform.cluster; import org.junit.Rule; diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterMock.java b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterMock.java index f3d9b98ffb8..eaa0db824df 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterMock.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/cluster/ClusterMock.java @@ -1,3 +1,22 @@ +/* + * 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.platform.cluster; public class ClusterMock implements Cluster { diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java new file mode 100644 index 00000000000..255dab8f8e3 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/MasterServletFilterTest.java @@ -0,0 +1,142 @@ +/* + * 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.platform.web; + +import java.io.IOException; +import java.util.Collections; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.web.ServletFilter; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +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; +import static org.mockito.Mockito.when; + +public class MasterServletFilterTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void resetSingleton() { + MasterServletFilter.INSTANCE = null; + } + + @Test + public void should_init_and_destroy_filters() throws Exception { + ServletFilter filter = mock(ServletFilter.class); + FilterConfig config = mock(FilterConfig.class); + MasterServletFilter master = new MasterServletFilter(); + master.init(config, singletonList(filter)); + + assertThat(master.getFilters()).containsOnly(filter); + verify(filter).init(config); + + master.destroy(); + verify(filter).destroy(); + } + + @Test + public void servlet_container_should_instantiate_only_a_single_master_instance() { + new MasterServletFilter(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Servlet filter org.sonar.server.platform.web.MasterServletFilter is already instantiated"); + new MasterServletFilter(); + } + + @Test + public void should_propagate_initialization_failure() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("foo"); + + ServletFilter filter = mock(ServletFilter.class); + doThrow(new IllegalStateException("foo")).when(filter).init(any(FilterConfig.class)); + + FilterConfig config = mock(FilterConfig.class); + MasterServletFilter filters = new MasterServletFilter(); + filters.init(config, singletonList(filter)); + } + + @Test + public void filters_should_be_optional() throws Exception { + FilterConfig config = mock(FilterConfig.class); + MasterServletFilter filters = new MasterServletFilter(); + filters.init(config, Collections.emptyList()); + + ServletRequest request = mock(HttpServletRequest.class); + ServletResponse response = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + filters.doFilter(request, response, chain); + + verify(chain).doFilter(request, response); + } + + @Test + public void should_keep_filter_ordering() throws Exception { + TrueFilter filter1 = new TrueFilter(); + TrueFilter filter2 = new TrueFilter(); + + MasterServletFilter filters = new MasterServletFilter(); + filters.init(mock(FilterConfig.class), asList(filter1, filter2)); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/foo/bar"); + when(request.getContextPath()).thenReturn(""); + ServletResponse response = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + filters.doFilter(request, response, chain); + + assertThat(filter1.count).isEqualTo(1); + assertThat(filter2.count).isEqualTo(2); + } + + private static final class TrueFilter extends ServletFilter { + private static int globalCount = 0; + private int count = 0; + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + globalCount++; + count = globalCount; + filterChain.doFilter(servletRequest, servletResponse); + } + + public void destroy() { + } + + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java new file mode 100644 index 00000000000..93c339c9986 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/ProfilingFilterTest.java @@ -0,0 +1,106 @@ +/* + * 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.platform.web; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.sonar.server.platform.web.ProfilingFilter; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProfilingFilterTest { + + private ProfilingFilter filter; + private FilterChain chain; + + @Before + public void initialize() throws Exception { + FilterConfig filterConfig = mock(FilterConfig.class); + when(filterConfig.getInitParameter("staticDirs")).thenReturn("/static,/assets"); + ServletContext context = mock(ServletContext.class); + when(context.getContextPath()).thenReturn("/context"); + when(filterConfig.getServletContext()).thenReturn(context); + chain = mock(FilterChain.class); + + filter = new ProfilingFilter(); + filter.init(filterConfig); + } + + @Test + public void should_profile_service_call() throws Exception { + filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); + } + + @Test + public void should_profile_service() throws Exception { + filter.doFilter(request("POST", "/context/service", null), null, chain); + } + + @Test + public void should_profile_context_root_as_slash2() throws Exception { + filter.doFilter(request("POST", "/context", null), null, chain); + } + + @Test(expected = ServletException.class) + public void should_profile_even_when_exception() throws Exception { + Mockito.doThrow(new ServletException()).when(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + filter.doFilter(request("POST", "/context/service/call", "param=value"), null, chain); + } + + @Test + public void should_not_profile_non_http_request() throws Exception { + filter.doFilter(mock(ServletRequest.class), null, chain); + } + + @Test + public void should_not_profile_static_resource() throws Exception { + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + } + + @Test + public void should_profile_static_resource_if_no_config() throws Exception { + FilterConfig filterConfig = mock(FilterConfig.class); + ServletContext context = mock(ServletContext.class); + when(context.getContextPath()).thenReturn("/context"); + when(filterConfig.getServletContext()).thenReturn(context); + + filter.init(filterConfig); + filter.doFilter(request("GET", "/context/static/image.png", null), null, chain); + } + + private HttpServletRequest request(String method, String path, String query) { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getMethod()).thenReturn(method); + when(request.getRequestURI()).thenReturn(path); + when(request.getQueryString()).thenReturn(query); + return request; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/RailsAppsDeployerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RailsAppsDeployerTest.java new file mode 100644 index 00000000000..937d5e8c29a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RailsAppsDeployerTest.java @@ -0,0 +1,116 @@ +/* + * 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.platform.web; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.platform.PluginRepository; +import org.sonar.server.platform.ServerFileSystem; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RailsAppsDeployerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void hasRubyRailsApp() throws Exception { + ClassLoader classLoader = new URLClassLoader(new URL[]{ + getClass().getResource("/org/sonar/server/platform/web/RailsAppsDeployerTest/FakeRubyRailsApp.jar").toURI().toURL()}, null); + + assertTrue(RailsAppsDeployer.hasRailsApp("fake", classLoader)); + assertFalse(RailsAppsDeployer.hasRailsApp("other", classLoader)); + } + + @Test + public void deployRubyRailsApp() throws Exception { + File tempDir = this.temp.getRoot(); + ClassLoader classLoader = new URLClassLoader(new URL[]{ + getClass().getResource("/org/sonar/server/platform/web/RailsAppsDeployerTest/FakeRubyRailsApp.jar").toURI().toURL()}, null); + + RailsAppsDeployer.deployRailsApp(tempDir, "fake", classLoader); + + File appDir = new File(tempDir, "fake"); + assertThat(appDir.isDirectory(), is(true)); + assertThat(appDir.exists(), is(true)); + assertThat(FileUtils.listFiles(appDir, null, true).size(), is(3)); + assertThat(new File(appDir, "init.rb").exists(), is(true)); + assertThat(new File(appDir, "app/controllers/fake_controller.rb").exists(), is(true)); + assertThat(new File(appDir, "app/views/fake/index.html.erb").exists(), is(true)); + } + + @Test + public void deployRubyRailsApps_no_apps() { + ServerFileSystem fileSystem = mock(ServerFileSystem.class); + File tempDir = this.temp.getRoot(); + when(fileSystem.getTempDir()).thenReturn(tempDir); + + PluginRepository pluginRepository = mock(PluginRepository.class); + when(pluginRepository.getPluginInfos()).thenReturn(Collections.emptyList()); + new RailsAppsDeployer(fileSystem, pluginRepository).start(); + + File appDir = new File(tempDir, "ror"); + assertThat(appDir.isDirectory(), is(true)); + assertThat(appDir.exists(), is(true)); + assertThat(FileUtils.listFiles(appDir, null, true).size(), is(0)); + } + + @Test + public void prepareRubyRailsRootDirectory() throws Exception { + ServerFileSystem fileSystem = mock(ServerFileSystem.class); + File tempDir = this.temp.getRoot(); + when(fileSystem.getTempDir()).thenReturn(tempDir); + + File dir = new RailsAppsDeployer(fileSystem, mock(PluginRepository.class)).prepareRailsDirectory(); + + assertThat(dir.isDirectory(), is(true)); + assertThat(dir.exists(), is(true)); + assertThat(dir.getCanonicalPath(), is(new File(tempDir, "ror").getCanonicalPath())); + } + + @Test + public void prepareRubyRailsRootDirectory_delete_existing_dir() throws Exception { + ServerFileSystem fileSystem = mock(ServerFileSystem.class); + File tempDir = this.temp.getRoot(); + when(fileSystem.getTempDir()).thenReturn(tempDir); + + File file = new File(tempDir, "ror/foo/bar.txt"); + FileUtils.writeStringToFile(file, "foooo"); + + File dir = new RailsAppsDeployer(fileSystem, mock(PluginRepository.class)).prepareRailsDirectory(); + + assertThat(dir.isDirectory(), is(true)); + assertThat(dir.exists(), is(true)); + assertThat(dir.getCanonicalPath(), is(new File(tempDir, "ror").getCanonicalPath())); + assertThat(FileUtils.listFiles(new File(tempDir, "ror"), null, true).size(), is(0)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/RoutesFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RoutesFilterTest.java new file mode 100644 index 00000000000..92d7dfbcb0f --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RoutesFilterTest.java @@ -0,0 +1,88 @@ +/* + * 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.platform.web; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; +import org.sonar.server.platform.web.RoutesFilter; + +public class RoutesFilterTest { + + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + + RoutesFilter underTest = new RoutesFilter(); + + @Before + public void setUp() throws Exception { + when(request.getContextPath()).thenReturn("/sonarqube"); + } + + @Test + public void send_redirect_when_url_contains_batch_with_jar() throws Exception { + when(request.getRequestURI()).thenReturn("/batch/file.jar"); + + underTest.doFilter(request, response, chain); + + verify(response).sendRedirect("/sonarqube/batch/file?name=file.jar"); + verifyZeroInteractions(chain); + } + + @Test + public void send_redirect_when_url_contains_batch_bootstrap() throws Exception { + when(request.getRequestURI()).thenReturn("/batch_bootstrap/index"); + + underTest.doFilter(request, response, chain); + + verify(response).sendRedirect("/sonarqube/batch/index"); + verifyZeroInteractions(chain); + } + + @Test + public void send_redirect_when_url_contains_api_sources() throws Exception { + when(request.getRequestURI()).thenReturn("/api/sources"); + when(request.getQueryString()).thenReturn("resource=my.project"); + + underTest.doFilter(request, response, chain); + + verify(response).sendRedirect("/sonarqube/api/sources/index?resource=my.project"); + verifyZeroInteractions(chain); + } + + @Test + public void does_not_redirect_and_execute_remaining_filter_on_unknown_path() throws Exception { + when(request.getRequestURI()).thenReturn("/api/issues/search"); + + underTest.doFilter(request, response, chain); + + verify(chain).doFilter(request, response); + verifyZeroInteractions(response); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/RubyRailsContextListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RubyRailsContextListenerTest.java new file mode 100644 index 00000000000..8a20e31e607 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/RubyRailsContextListenerTest.java @@ -0,0 +1,74 @@ +/* + * 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.platform.web; + +import java.io.IOException; +import javax.servlet.ServletContextEvent; +import org.jruby.rack.RackApplicationFactory; +import org.jruby.rack.servlet.ServletRackContext; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.server.platform.web.PlatformServletContextListener; +import org.sonar.server.platform.web.RubyRailsContextListener; + +import static java.lang.Boolean.TRUE; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RubyRailsContextListenerTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + ServletContextEvent event = mock(ServletContextEvent.class, Mockito.RETURNS_DEEP_STUBS); + RubyRailsContextListener underTest = new RubyRailsContextListener(); + + @Test + public void do_not_initialize_rails_if_error_during_startup() { + when(event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE)).thenReturn(null); + + underTest.contextInitialized(event); + + verify(event.getServletContext(), never()).setAttribute(anyString(), anyObject()); + } + + @Test + public void initialize_rails_if_no_errors_during_startup() { + when(event.getServletContext().getAttribute(PlatformServletContextListener.STARTED_ATTRIBUTE)).thenReturn(TRUE); + underTest.contextInitialized(event); + // Ruby environment is started + // See RailsServletContextListener -> RackServletContextListener + verify(event.getServletContext()).setAttribute(eq("rack.factory"), anyObject()); + } + + @Test + public void always_propagates_initialization_errors() { + expectedException.expect(RuntimeException.class); + + underTest.handleInitializationException(new IOException(), mock(RackApplicationFactory.class), mock(ServletRackContext.class)); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/web/SecurityServletFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/web/SecurityServletFilterTest.java new file mode 100644 index 00000000000..72d499ff4c0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/web/SecurityServletFilterTest.java @@ -0,0 +1,104 @@ +/* + * 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.platform.web; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Test; +import org.sonar.server.platform.web.SecurityServletFilter; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SecurityServletFilterTest { + + SecurityServletFilter underTest = new SecurityServletFilter(); + HttpServletResponse response = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + + @Test + public void allow_GET_method() throws IOException, ServletException { + assertThatMethodIsAllowed("GET"); + } + + @Test + public void allow_HEAD_method() throws IOException, ServletException { + assertThatMethodIsAllowed("HEAD"); + } + + @Test + public void allow_PUT_method() throws IOException, ServletException { + assertThatMethodIsAllowed("PUT"); + } + + @Test + public void allow_POST_method() throws IOException, ServletException { + assertThatMethodIsAllowed("POST"); + } + + private void assertThatMethodIsAllowed(String httpMethod) throws IOException, ServletException { + HttpServletRequest request = newRequest(httpMethod); + underTest.doFilter(request, response, chain); + verify(response, never()).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + verify(chain).doFilter(request, response); + } + + @Test + public void deny_OPTIONS_method() throws IOException, ServletException { + assertThatMethodIsDenied("OPTIONS"); + } + + @Test + public void deny_TRACE_method() throws IOException, ServletException { + assertThatMethodIsDenied("TRACE"); + } + + private void assertThatMethodIsDenied(String httpMethod) throws IOException, ServletException { + underTest.doFilter(newRequest(httpMethod), response, chain); + verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + + @Test + public void set_secured_headers() throws ServletException, IOException { + underTest.init(mock(FilterConfig.class)); + HttpServletRequest request = newRequest("GET"); + + underTest.doFilter(request, response, chain); + + verify(response, times(3)).addHeader(startsWith("X-"), anyString()); + + underTest.destroy(); + } + + private HttpServletRequest newRequest(String httpMethod) { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getMethod()).thenReturn(httpMethod); + return req; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java index 82aeee20103..15425dfd2d4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java @@ -35,7 +35,7 @@ import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.SonarException; import org.sonar.core.platform.PluginInfo; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.UpdateCenter; @@ -86,11 +86,11 @@ public class PluginDownloaderTest { } }).when(httpDownloader).download(any(URI.class), any(File.class)); - DefaultServerFileSystem defaultServerFileSystem = mock(DefaultServerFileSystem.class); + ServerFileSystem fs = mock(ServerFileSystem.class); downloadDir = testFolder.newFolder("downloads"); - when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir); + when(fs.getDownloadedPluginsDir()).thenReturn(downloadDir); - pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem); + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, fs); } @After @@ -163,12 +163,12 @@ public class PluginDownloaderTest { @Test public void throw_exception_if_download_dir_is_invalid() throws Exception { - DefaultServerFileSystem defaultServerFileSystem = mock(DefaultServerFileSystem.class); + ServerFileSystem fs = mock(ServerFileSystem.class); // download dir is a file instead of being a directory File downloadDir = testFolder.newFile(); - when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir); + when(fs.getDownloadedPluginsDir()).thenReturn(downloadDir); - pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem); + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, fs); try { pluginDownloader.start(); fail(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java index 0239c1452b5..ecc861a4117 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java @@ -19,14 +19,13 @@ */ package org.sonar.server.plugins; +import java.io.File; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.ExplodedPlugin; -import org.sonar.server.platform.DefaultServerFileSystem; - -import java.io.File; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.platform.ServerFileSystem; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -37,7 +36,7 @@ public class ServerPluginJarExploderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class); + ServerFileSystem fs = mock(ServerFileSystem.class); ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs); @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java index 5393c4268e7..51bc9ebe563 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -39,7 +39,7 @@ import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.LogTester; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginLoader; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.updatecenter.common.Version; import static org.assertj.core.api.Assertions.assertThat; @@ -60,7 +60,7 @@ public class ServerPluginRepositoryTest { Server server = mock(Server.class); ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class); - DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); + ServerFileSystem fs = mock(ServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); PluginLoader pluginLoader = mock(PluginLoader.class); ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader); @@ -384,7 +384,7 @@ public class ServerPluginRepositoryTest { underTest.getPluginInfos(); } - + private File copyTestPluginTo(String testPluginName, File toDir) throws IOException { File jar = TestProjectUtils.jarOf(testPluginName); // file is copied because it's supposed to be moved by the test diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java index 959792795c6..e21503eea16 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java @@ -19,6 +19,10 @@ */ package org.sonar.server.startup; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; import org.apache.commons.io.FileUtils; import org.hamcrest.core.Is; import org.junit.Before; @@ -27,12 +31,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; -import org.sonar.server.platform.DefaultServerFileSystem; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; +import org.sonar.server.platform.ServerFileSystem; import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.containsString; @@ -44,14 +43,13 @@ public class GeneratePluginIndexTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - DefaultServerFileSystem fileSystem; - File index; + private ServerFileSystem fileSystem = mock(ServerFileSystem.class); + private File underTest; @Before public void createIndexFile() { - fileSystem = mock(DefaultServerFileSystem.class); - index = new File("target/test-tmp/GeneratePluginIndexTest/plugins.txt"); - when(fileSystem.getPluginIndex()).thenReturn(index); + when(fileSystem.getPluginIndex()).thenReturn(underTest); + underTest = new File("target/test-tmp/GeneratePluginIndexTest/plugins.txt"); } @Test @@ -63,7 +61,7 @@ public class GeneratePluginIndexTest { new GeneratePluginIndex(fileSystem, repository).start(); - List lines = FileUtils.readLines(index); + List lines = FileUtils.readLines(underTest); assertThat(lines.size(), Is.is(2)); assertThat(lines.get(0), containsString("sqale")); assertThat(lines.get(1), containsString("checkstyle")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterServletFiltersTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterServletFiltersTest.java index d8d61c1bd22..86f6bafe0b0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterServletFiltersTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterServletFiltersTest.java @@ -21,9 +21,10 @@ package org.sonar.server.startup; import org.junit.Test; import org.sonar.api.web.ServletFilter; -import org.sonar.server.platform.MasterServletFilter; +import org.sonar.server.platform.web.MasterServletFilter; import javax.servlet.ServletException; +import org.sonar.server.platform.web.RegisterServletFilters; import static org.mockito.Matchers.anyListOf; import static org.mockito.Mockito.mock; diff --git a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UpdateCenterWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UpdateCenterWsTest.java index 78b5140f8ff..0d25e12ba8d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UpdateCenterWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UpdateCenterWsTest.java @@ -19,16 +19,16 @@ */ package org.sonar.server.updatecenter.ws; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - import org.junit.Before; import org.junit.Test; import org.sonar.api.server.ws.RailsHandler; import org.sonar.api.server.ws.WebService; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.server.ws.WsTester; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + public class UpdateCenterWsTest { WsTester tester; @@ -36,7 +36,7 @@ public class UpdateCenterWsTest { @Before public void setUp() { tester = new WsTester(new UpdateCenterWs( - new UploadAction(null, mock(DefaultServerFileSystem.class)))); + new UploadAction(null, mock(ServerFileSystem.class)))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java index 14589593da6..41163d4a98a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java @@ -20,14 +20,6 @@ package org.sonar.server.updatecenter.ws; -import static java.nio.file.Files.newInputStream; -import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.core.permission.GlobalPermissions.PROVISIONING; -import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; -import static org.sonar.test.ExceptionCauseMatcher.hasType; - import java.io.File; import java.io.InputStream; import java.nio.channels.ClosedChannelException; @@ -37,11 +29,19 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.server.platform.ServerFileSystem; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; +import static java.nio.file.Files.newInputStream; +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.permission.GlobalPermissions.PROVISIONING; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.test.ExceptionCauseMatcher.hasType; + public class UploadActionTest { static final String PLUGIN_NAME = "plugin.jar"; @@ -55,7 +55,7 @@ public class UploadActionTest { @Rule public UserSessionRule userSession = UserSessionRule.standalone(); - DefaultServerFileSystem fileSystem = mock(DefaultServerFileSystem.class); + ServerFileSystem fileSystem = mock(ServerFileSystem.class); File pluginDirectory; File plugin = new File(getClass().getResource("UploadActionTest/plugin.jar").getFile()); diff --git a/server/sonar-server/src/test/resources/org/sonar/server/platform/RailsAppsDeployerTest/FakeRubyRailsApp.jar b/server/sonar-server/src/test/resources/org/sonar/server/platform/RailsAppsDeployerTest/FakeRubyRailsApp.jar deleted file mode 100644 index 9ed866620ec..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/platform/RailsAppsDeployerTest/FakeRubyRailsApp.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/platform/web/RailsAppsDeployerTest/FakeRubyRailsApp.jar b/server/sonar-server/src/test/resources/org/sonar/server/platform/web/RailsAppsDeployerTest/FakeRubyRailsApp.jar new file mode 100644 index 00000000000..9ed866620ec Binary files /dev/null and b/server/sonar-server/src/test/resources/org/sonar/server/platform/web/RailsAppsDeployerTest/FakeRubyRailsApp.jar differ diff --git a/server/sonar-web/src/main/webapp/WEB-INF/web.xml b/server/sonar-web/src/main/webapp/WEB-INF/web.xml index bed9e8b5cec..b86c1a99b9b 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/web.xml +++ b/server/sonar-web/src/main/webapp/WEB-INF/web.xml @@ -29,7 +29,7 @@ ServletFilters - org.sonar.server.platform.MasterServletFilter + org.sonar.server.platform.web.MasterServletFilter UserSessionFilter @@ -50,11 +50,11 @@ SecurityFilter - org.sonar.server.platform.SecurityServletFilter + org.sonar.server.platform.web.SecurityServletFilter ProfilingFilter - org.sonar.server.platform.ProfilingFilter + org.sonar.server.platform.web.ProfilingFilter staticDirs /images,/javascripts,/stylesheets @@ -62,7 +62,7 @@ RoutesFilter - org.sonar.server.platform.RoutesFilter + org.sonar.server.platform.web.RoutesFilter @@ -110,10 +110,10 @@ - org.sonar.server.platform.PlatformServletContextListener + org.sonar.server.platform.web.PlatformServletContextListener - org.sonar.server.platform.RubyRailsContextListener + org.sonar.server.platform.web.RubyRailsContextListener diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ServerFileSystem.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ServerFileSystem.java index 334b663ea88..fc84199973d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ServerFileSystem.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ServerFileSystem.java @@ -30,8 +30,16 @@ import org.sonar.api.server.ServerSide; @ComputeEngineSide public interface ServerFileSystem { + /** + * Root directory of the server installation + * @return an existing directory + */ File getHomeDir(); + /** + * Temporary directory, clean up on restarts + * @return an existing directory + */ File getTempDir(); }