From 46464911a15220d9ca866488922d1645fa9f5e65 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 22 Mar 2012 19:30:15 +0100 Subject: SONAR-3224 remove the need for server restart --- .../server/platform/DefaultServerFileSystem.java | 16 +-- .../java/org/sonar/server/platform/Platform.java | 9 +- .../sonar/server/plugins/ApplicationDeployer.java | 118 +++++++++++++++++++++ .../org/sonar/server/plugins/ClassLoaderUtils.java | 107 +++++++++++++++++++ .../org/sonar/server/plugins/PluginDeployer.java | 10 +- .../sonar/server/startup/ApplicationDeployer.java | 113 -------------------- .../org/sonar/server/startup/ClassLoaderUtils.java | 107 ------------------- 7 files changed, 243 insertions(+), 237 deletions(-) create mode 100644 sonar-server/src/main/java/org/sonar/server/plugins/ApplicationDeployer.java create mode 100644 sonar-server/src/main/java/org/sonar/server/plugins/ClassLoaderUtils.java delete mode 100644 sonar-server/src/main/java/org/sonar/server/startup/ApplicationDeployer.java delete mode 100644 sonar-server/src/main/java/org/sonar/server/startup/ClassLoaderUtils.java (limited to 'sonar-server/src/main/java') diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java index 17f66d2abbb..0560513eff2 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.platform.ServerFileSystem; -import org.sonar.jpa.session.DatabaseConnector; +import org.sonar.core.persistence.Database; import java.io.File; import java.io.FileFilter; @@ -45,12 +45,12 @@ public class DefaultServerFileSystem implements ServerFileSystem { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultServerFileSystem.class); - private DatabaseConnector databaseConnector; + private Database database; private File deployDir; private File homeDir; - public DefaultServerFileSystem(DatabaseConnector databaseConnector, Settings settings) { - this.databaseConnector = databaseConnector; + public DefaultServerFileSystem(Database database, Settings settings) { + this.database = database; this.homeDir = new File(settings.getString(CoreProperties.SONAR_HOME)); String deployPath = settings.getString(ServerSettings.DEPLOY_DIR); @@ -62,8 +62,8 @@ public class DefaultServerFileSystem implements ServerFileSystem { /** * for unit tests */ - public DefaultServerFileSystem(DatabaseConnector databaseConnector, File homeDir, File deployDir) { - this.databaseConnector = databaseConnector; + public DefaultServerFileSystem(Database database, File homeDir, File deployDir) { + this.database = database; this.deployDir = deployDir; this.homeDir = homeDir; } @@ -102,7 +102,7 @@ public class DefaultServerFileSystem implements ServerFileSystem { public File getHomeDir() { return homeDir; } - + public File getTempDir() { return new File(homeDir, "temp"); } @@ -128,7 +128,7 @@ public class DefaultServerFileSystem implements ServerFileSystem { } public File getJdbcDriver() { - String dialect = databaseConnector.getDialect().getId(); + String dialect = database.getDialect().getId(); File dir = new File(getHomeDir(), "/extensions/jdbc-driver/" + dialect + "/"); List jars = getFiles(dir, "jar"); if (jars.isEmpty()) { diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index f479bab8123..682cd208416 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -141,6 +141,10 @@ public final class Platform { for (Class daoClass : DaoUtils.getDaoClasses()) { rootContainer.addSingleton(daoClass); } + rootContainer.addSingleton(PluginDeployer.class); + rootContainer.addSingleton(DefaultServerPluginRepository.class); + rootContainer.addSingleton(DefaultServerFileSystem.class); + rootContainer.addSingleton(ApplicationDeployer.class); rootContainer.startComponents(); } @@ -153,10 +157,8 @@ public final class Platform { coreContainer = rootContainer.createChild(); coreContainer.addSingleton(ServerDatabaseSettingsLoader.class); coreContainer.addSingleton(DefaultDatabaseConnector.class); - coreContainer.addSingleton(PluginDeployer.class); - coreContainer.addSingleton(DefaultServerPluginRepository.class); + coreContainer.addSingleton(ServerExtensionInstaller.class); - coreContainer.addSingleton(DefaultServerFileSystem.class); coreContainer.addSingleton(ThreadLocalDatabaseSessionFactory.class); coreContainer.addPicoAdapter(new DatabaseSessionProvider()); coreContainer.startComponents(); @@ -220,7 +222,6 @@ public final class Platform { private void executeStartupTasks() { ComponentContainer startupContainer = servicesContainer.createChild(); startupContainer.addSingleton(GwtPublisher.class); - startupContainer.addSingleton(ApplicationDeployer.class); startupContainer.addSingleton(RegisterMetrics.class); startupContainer.addSingleton(RegisterRules.class); startupContainer.addSingleton(RegisterProvidedProfiles.class); diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/ApplicationDeployer.java b/sonar-server/src/main/java/org/sonar/server/plugins/ApplicationDeployer.java new file mode 100644 index 00000000000..ea22a3dae35 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/plugins/ApplicationDeployer.java @@ -0,0 +1,118 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.server.plugins; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.platform.PluginMetadata; +import org.sonar.api.platform.PluginRepository; +import org.sonar.api.platform.ServerFileSystem; +import org.sonar.server.plugins.ClassLoaderUtils; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; + +/** + * 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 2.15 + */ +public class ApplicationDeployer { + private static final Logger LOG = LoggerFactory.getLogger(ApplicationDeployer.class); + private static final String ROR_PATH = "org/sonar/ror/"; + + private ServerFileSystem fileSystem; + private PluginRepository pluginRepository; + + public ApplicationDeployer(ServerFileSystem fileSystem, PluginRepository pluginRepository) { + this.fileSystem = fileSystem; + this.pluginRepository = pluginRepository; + } + + public void start() throws IOException { + deployRubyRailsApps(); + } + + private void deployRubyRailsApps() { + LOG.info("Deploy Ruby on Rails applications"); + File appsDir = prepareRubyRailsRootDirectory(); + + for (PluginMetadata pluginMetadata : pluginRepository.getMetadata()) { + String pluginKey = pluginMetadata.getKey(); + try { + deployRubyRailsApp(appsDir, pluginKey, pluginRepository.getPlugin(pluginKey).getClass().getClassLoader()); + } catch (Exception e) { + throw new IllegalStateException("Fail to deploy Ruby on Rails application: " + pluginKey, e); + } + } + } + + @VisibleForTesting + File prepareRubyRailsRootDirectory() { + File appsDir = new File(fileSystem.getTempDir(), "ror"); + prepareDir(appsDir); + return appsDir; + } + + @VisibleForTesting + static void deployRubyRailsApp(File appsDir, final String pluginKey, ClassLoader appClassLoader) { + if (hasRubyRailsApp(pluginKey, appClassLoader)) { + LOG.info("Deploy app: " + pluginKey); + File appDir = new File(appsDir, pluginKey); + ClassLoaderUtils.copyResources(appClassLoader, ROR_PATH + 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 + "/"); + } + }); + } + } + + @VisibleForTesting + static boolean hasRubyRailsApp(String pluginKey, ClassLoader classLoader) { + return classLoader.getResource(ROR_PATH + pluginKey) != null; + + } + + private void prepareDir(File appsDir) { + if (appsDir.exists() && appsDir.isDirectory()) { + try { + FileUtils.deleteDirectory(appsDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to delete temp directory: " + appsDir); + } + } + try { + FileUtils.forceMkdir(appsDir); + } catch (IOException e) { + throw new IllegalStateException("Fail to create temp directory: " + appsDir); + } + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/ClassLoaderUtils.java b/sonar-server/src/main/java/org/sonar/server/plugins/ClassLoaderUtils.java new file mode 100644 index 00000000000..66bfed510cb --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/plugins/ClassLoaderUtils.java @@ -0,0 +1,107 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.server.plugins; + +import com.google.common.base.*; +import com.google.common.collect.Lists; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.CharEncoding; +import org.apache.commons.lang.StringUtils; + +import javax.annotation.Nullable; +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; + +/** + * TODO it this class needed in sonar-plugin-api ? + * + * @since 2.15 + */ +public final class ClassLoaderUtils { + + private ClassLoaderUtils() { + } + + public static File copyResources(ClassLoader classLoader, String rootPath, File toDir) { + return copyResources(classLoader, rootPath, toDir, Functions.identity()); + } + + 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()); + } + } + + return toDir; + } + + public static Collection listFiles(ClassLoader classLoader, String rootPath) { + return listResources(classLoader, rootPath, new Predicate() { + @Override + public boolean apply(@Nullable String path) { + return !StringUtils.endsWith(path, "/"); + } + }); + } + + public static Collection listResources(ClassLoader classloader, String rootPath) { + return listResources(classloader, rootPath, Predicates.alwaysTrue()); + } + + public static Collection listResources(ClassLoader classloader, String rootPath, Predicate predicate) { + try { + Collection paths = Lists.newArrayList(); + rootPath = StringUtils.removeStart(rootPath, "/"); + + URL root = classloader.getResource(rootPath); + if (root == null) { + return paths; + } + if (!"jar".equals(root.getProtocol())) { + throw new IllegalStateException("Unsupported protocol: " + root.getProtocol()); + } + String jarPath = root.getPath().substring(5, root.getPath().indexOf("!")); //strip out only the JAR file + JarFile jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8)); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + String name = entries.nextElement().getName(); + if (name.startsWith(rootPath) && predicate.apply(name)) { + paths.add(name); + } + } + return paths; + } catch (Exception e) { + throw Throwables.propagate(e); + } + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/PluginDeployer.java b/sonar-server/src/main/java/org/sonar/server/plugins/PluginDeployer.java index d098265f977..df05348967d 100644 --- a/sonar-server/src/main/java/org/sonar/server/plugins/PluginDeployer.java +++ b/sonar-server/src/main/java/org/sonar/server/plugins/PluginDeployer.java @@ -47,15 +47,15 @@ public class PluginDeployer implements ServerComponent { private DefaultServerFileSystem fileSystem; private Map pluginByKeys = Maps.newHashMap(); - private PluginInstaller extractor; + private PluginInstaller installer; public PluginDeployer(DefaultServerFileSystem fileSystem) { this(fileSystem, new PluginInstaller()); } - PluginDeployer(DefaultServerFileSystem fileSystem, PluginInstaller extractor) { + PluginDeployer(DefaultServerFileSystem fileSystem, PluginInstaller installer) { this.fileSystem = fileSystem; - this.extractor = extractor; + this.installer = installer; } public void start() throws IOException { @@ -90,7 +90,7 @@ public class PluginDeployer implements ServerComponent { } private void registerPlugin(File file, boolean isCore, boolean canDelete) throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(file, isCore); + DefaultPluginMetadata metadata = installer.extractMetadata(file, isCore); if (StringUtils.isNotBlank(metadata.getKey())) { PluginMetadata existing = pluginByKeys.get(metadata.getKey()); if (existing != null) { @@ -198,7 +198,7 @@ public class PluginDeployer implements ServerComponent { plugin.addDeprecatedExtension(deprecatedExtension); } - extractor.install(plugin, pluginDeployDir); + installer.install(plugin, pluginDeployDir); } catch (IOException e) { throw new RuntimeException("Fail to deploy the plugin " + plugin, e); diff --git a/sonar-server/src/main/java/org/sonar/server/startup/ApplicationDeployer.java b/sonar-server/src/main/java/org/sonar/server/startup/ApplicationDeployer.java deleted file mode 100644 index 5d3165abbbf..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/startup/ApplicationDeployer.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.server.startup; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.platform.ServerFileSystem; -import org.sonar.api.web.RubyRailsApp; - -import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; - -/** - * 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 2.15 - */ -public class ApplicationDeployer { - private static final Logger LOG = LoggerFactory.getLogger(ApplicationDeployer.class); - - private ServerFileSystem fileSystem; - private RubyRailsApp[] apps; - - public ApplicationDeployer(ServerFileSystem fileSystem, RubyRailsApp[] apps) { - this.fileSystem = fileSystem; - this.apps = apps; - } - - public ApplicationDeployer(ServerFileSystem fileSystem) { - this(fileSystem, new RubyRailsApp[0]); - } - - public void start() throws IOException { - deployRubyRailsApps(); - } - - private void deployRubyRailsApps() { - LOG.info("Deploy Ruby on Rails applications"); - File appsDir = prepareRubyRailsRootDirectory(); - - for (final RubyRailsApp app : apps) { - try { - deployRubyRailsApp(appsDir, app, app.getClass().getClassLoader()); - } catch (Exception e) { - throw new IllegalStateException("Fail to deploy Ruby on Rails application: " + app.getKey(), e); - } - } - } - - @VisibleForTesting - File prepareRubyRailsRootDirectory() { - File appsDir = new File(fileSystem.getTempDir(), "ror"); - prepareDir(appsDir); - return appsDir; - } - - @VisibleForTesting - static void deployRubyRailsApp(File appsDir, final RubyRailsApp app, ClassLoader appClassLoader) { - LOG.debug("Deploy: " + app.getKey()); - File appDir = new File(appsDir, app.getKey()); - if (appDir.exists()) { - LOG.error("Ruby on Rails application already exists: " + app.getKey()); - } else { - ClassLoaderUtils.copyResources(appClassLoader, app.getPath(), appDir, new Function() { - @Override - public String apply(@Nullable String relativePath) { - // relativePath format is: org/sonar/sqale/app/controllers/foo_controller.rb - // app path is: /org/sonar/sqale - // -> deployed file is app/controllers/foo_controller.rb - return StringUtils.substringAfter(relativePath, StringUtils.removeStart(app.getPath(), "/") + "/"); - } - }); - } - } - - private void prepareDir(File appsDir) { - if (appsDir.exists() && appsDir.isDirectory()) { - try { - FileUtils.deleteDirectory(appsDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to delete temp directory: " + appsDir); - } - } - try { - FileUtils.forceMkdir(appsDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to create temp directory: " + appsDir); - } - } -} diff --git a/sonar-server/src/main/java/org/sonar/server/startup/ClassLoaderUtils.java b/sonar-server/src/main/java/org/sonar/server/startup/ClassLoaderUtils.java deleted file mode 100644 index 95e95b26cff..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/startup/ClassLoaderUtils.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.server.startup; - -import com.google.common.base.*; -import com.google.common.collect.Lists; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.CharEncoding; -import org.apache.commons.lang.StringUtils; - -import javax.annotation.Nullable; -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; - -/** - * TODO it this class needed in sonar-plugin-api ? - * - * @since 2.15 - */ -public final class ClassLoaderUtils { - - private ClassLoaderUtils() { - } - - public static File copyResources(ClassLoader classLoader, String rootPath, File toDir) { - return copyResources(classLoader, rootPath, toDir, Functions.identity()); - } - - 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()); - } - } - - return toDir; - } - - public static Collection listFiles(ClassLoader classLoader, String rootPath) { - return listResources(classLoader, rootPath, new Predicate() { - @Override - public boolean apply(@Nullable String path) { - return !StringUtils.endsWith(path, "/"); - } - }); - } - - public static Collection listResources(ClassLoader classloader, String rootPath) { - return listResources(classloader, rootPath, Predicates.alwaysTrue()); - } - - public static Collection listResources(ClassLoader classloader, String rootPath, Predicate predicate) { - try { - Collection paths = Lists.newArrayList(); - rootPath = StringUtils.removeStart(rootPath, "/"); - - URL root = classloader.getResource(rootPath); - if (root == null) { - return paths; - } - if (!"jar".equals(root.getProtocol())) { - throw new IllegalStateException("Unsupported protocol: " + root.getProtocol()); - } - String jarPath = root.getPath().substring(5, root.getPath().indexOf("!")); //strip out only the JAR file - JarFile jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8)); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - String name = entries.nextElement().getName(); - if (name.startsWith(rootPath) && predicate.apply(name)) { - paths.add(name); - } - } - return paths; - } catch (Exception e) { - throw Throwables.propagate(e); - } - } -} -- cgit v1.2.3