--- /dev/null
+This directory contains temporary data required by server. Do not clean up when server is up.
\ No newline at end of file
import org.sonar.api.platform.PluginMetadata;
import org.sonar.api.platform.PluginRepository;
import org.sonar.core.plugins.PluginClassloaders;
-import org.sonar.core.plugins.PluginFileExtractor;
+import org.sonar.core.plugins.PluginInstaller;
import org.sonar.core.plugins.RemotePlugin;
import java.io.File;
}
void doStart(List<RemotePlugin> remotePlugins) {
- PluginFileExtractor extractor = new PluginFileExtractor();
+ PluginInstaller extractor = new PluginInstaller();
metadataByKey = Maps.newHashMap();
for (RemotePlugin remote : remotePlugins) {
if (isAccepted(remote.getKey())) {
+++ /dev/null
-/*
- * 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.core.plugins;
-
-import org.apache.commons.io.FileUtils;
-import org.sonar.api.Plugin;
-import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.ZipUtils;
-import org.sonar.updatecenter.common.PluginKeyUtils;
-import org.sonar.updatecenter.common.PluginManifest;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.List;
-import java.util.zip.ZipEntry;
-
-public class PluginFileExtractor {
-
- public DefaultPluginMetadata installInSameLocation(File pluginFile, boolean isCore, List<File> deprecatedExtensions) {
- return install(pluginFile, isCore, deprecatedExtensions, null);
- }
-
- public DefaultPluginMetadata install(File pluginFile, boolean isCore, List<File> deprecatedExtensions, File toDir) {
- DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore);
- metadata.setDeprecatedExtensions(deprecatedExtensions);
- return install(metadata, toDir);
- }
-
- public DefaultPluginMetadata install(DefaultPluginMetadata metadata, File toDir) {
- try {
- File pluginFile = metadata.getFile();
- File pluginBasedir;
- if (toDir != null) {
- pluginBasedir = toDir;
- FileUtils.forceMkdir(pluginBasedir);
- File targetFile = new File(pluginBasedir, pluginFile.getName());
- FileUtils.copyFile(pluginFile, targetFile);
- metadata.addDeployedFile(targetFile);
- } else {
- pluginBasedir = pluginFile.getParentFile();
- metadata.addDeployedFile(pluginFile);
- }
-
- if (metadata.getPathsToInternalDeps().length > 0) {
- // needs to unzip the jar
- ZipUtils.unzip(pluginFile, pluginBasedir, new ZipUtils.ZipEntryFilter() {
- public boolean accept(ZipEntry entry) {
- return entry.getName().startsWith("META-INF/lib");
- }
- });
- for (String depPath : metadata.getPathsToInternalDeps()) {
- File dependency = new File(pluginBasedir, depPath);
- if (!dependency.isFile() || !dependency.exists()) {
- throw new IllegalArgumentException("Dependency " + depPath + " can not be found in " + pluginFile.getName());
- }
- metadata.addDeployedFile(dependency);
- }
- }
-
- for (File extension : metadata.getDeprecatedExtensions()) {
- File toFile = new File(pluginBasedir, extension.getName());
- if (!toFile.equals(extension)) {
- FileUtils.copyFile(extension, toFile);
- }
- metadata.addDeployedFile(toFile);
- }
-
- return metadata;
-
- } catch (IOException e) {
- throw new SonarException("Fail to install plugin: " + metadata, e);
- }
- }
-
- public DefaultPluginMetadata extractMetadata(File file, boolean isCore) {
- try {
- PluginManifest manifest = new PluginManifest(file);
- DefaultPluginMetadata metadata = DefaultPluginMetadata.create(file);
- metadata.setKey(manifest.getKey());
- metadata.setName(manifest.getName());
- metadata.setDescription(manifest.getDescription());
- metadata.setLicense(manifest.getLicense());
- metadata.setOrganization(manifest.getOrganization());
- metadata.setOrganizationUrl(manifest.getOrganizationUrl());
- metadata.setMainClass(manifest.getMainClass());
- metadata.setVersion(manifest.getVersion());
- metadata.setHomepage(manifest.getHomepage());
- metadata.setPathsToInternalDeps(manifest.getDependencies());
- metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
- metadata.setBasePlugin(manifest.getBasePlugin());
- metadata.setCore(isCore);
- if (metadata.isOldManifest()) {
- completeDeprecatedMetadata(metadata);
- }
- return metadata;
-
- } catch (IOException e) {
- throw new IllegalStateException("Fail to extract plugin metadata from file: " + file, e);
- }
- }
-
- private void completeDeprecatedMetadata(DefaultPluginMetadata metadata) throws IOException {
- String mainClass = metadata.getMainClass();
- File pluginFile = metadata.getFile();
- try {
- // copy file in a temp directory because Windows+Oracle JVM Classloader lock the JAR file
- File tempFile = File.createTempFile(pluginFile.getName(), null);
- FileUtils.copyFile(pluginFile, tempFile);
-
- URLClassLoader pluginClassLoader = URLClassLoader.newInstance(new URL[]{tempFile.toURI().toURL()}, getClass().getClassLoader());
- Plugin pluginInstance = (Plugin) pluginClassLoader.loadClass(mainClass).newInstance();
- metadata.setKey(PluginKeyUtils.sanitize(pluginInstance.getKey()));
- metadata.setDescription(pluginInstance.getDescription());
- metadata.setName(pluginInstance.getName());
-
- } catch (Exception e) {
- throw new RuntimeException("The metadata main class can not be created. Plugin file=" + pluginFile.getName() + ", class=" + mainClass, e);
- }
- }
-}
--- /dev/null
+/*
+ * 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.core.plugins;
+
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.ZipUtils;
+import org.sonar.updatecenter.common.PluginKeyUtils;
+import org.sonar.updatecenter.common.PluginManifest;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+public class PluginInstaller {
+
+ public DefaultPluginMetadata installInSameLocation(File pluginFile, boolean isCore, List<File> deprecatedExtensions) {
+ return install(pluginFile, isCore, deprecatedExtensions, null);
+ }
+
+ public DefaultPluginMetadata install(File pluginFile, boolean isCore, List<File> deprecatedExtensions, File toDir) {
+ DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore);
+ metadata.setDeprecatedExtensions(deprecatedExtensions);
+ return install(metadata, toDir);
+ }
+
+ public DefaultPluginMetadata install(DefaultPluginMetadata metadata, File toDir) {
+ try {
+ File pluginFile = metadata.getFile();
+ File pluginBasedir;
+ if (toDir != null) {
+ pluginBasedir = toDir;
+ FileUtils.forceMkdir(pluginBasedir);
+ File targetFile = new File(pluginBasedir, pluginFile.getName());
+ FileUtils.copyFile(pluginFile, targetFile);
+ metadata.addDeployedFile(targetFile);
+ } else {
+ pluginBasedir = pluginFile.getParentFile();
+ metadata.addDeployedFile(pluginFile);
+ }
+
+ if (metadata.getPathsToInternalDeps().length > 0) {
+ // needs to unzip the jar
+ ZipUtils.unzip(pluginFile, pluginBasedir, new ZipUtils.ZipEntryFilter() {
+ public boolean accept(ZipEntry entry) {
+ return entry.getName().startsWith("META-INF/lib");
+ }
+ });
+ for (String depPath : metadata.getPathsToInternalDeps()) {
+ File dependency = new File(pluginBasedir, depPath);
+ if (!dependency.isFile() || !dependency.exists()) {
+ throw new IllegalArgumentException("Dependency " + depPath + " can not be found in " + pluginFile.getName());
+ }
+ metadata.addDeployedFile(dependency);
+ }
+ }
+
+ for (File extension : metadata.getDeprecatedExtensions()) {
+ File toFile = new File(pluginBasedir, extension.getName());
+ if (!toFile.equals(extension)) {
+ FileUtils.copyFile(extension, toFile);
+ }
+ metadata.addDeployedFile(toFile);
+ }
+
+ return metadata;
+
+ } catch (IOException e) {
+ throw new SonarException("Fail to install plugin: " + metadata, e);
+ }
+ }
+
+ public DefaultPluginMetadata extractMetadata(File file, boolean isCore) {
+ try {
+ PluginManifest manifest = new PluginManifest(file);
+ DefaultPluginMetadata metadata = DefaultPluginMetadata.create(file);
+ metadata.setKey(manifest.getKey());
+ metadata.setName(manifest.getName());
+ metadata.setDescription(manifest.getDescription());
+ metadata.setLicense(manifest.getLicense());
+ metadata.setOrganization(manifest.getOrganization());
+ metadata.setOrganizationUrl(manifest.getOrganizationUrl());
+ metadata.setMainClass(manifest.getMainClass());
+ metadata.setVersion(manifest.getVersion());
+ metadata.setHomepage(manifest.getHomepage());
+ metadata.setPathsToInternalDeps(manifest.getDependencies());
+ metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
+ metadata.setBasePlugin(manifest.getBasePlugin());
+ metadata.setCore(isCore);
+ if (metadata.isOldManifest()) {
+ completeDeprecatedMetadata(metadata);
+ }
+ return metadata;
+
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to extract plugin metadata from file: " + file, e);
+ }
+ }
+
+ private void completeDeprecatedMetadata(DefaultPluginMetadata metadata) throws IOException {
+ String mainClass = metadata.getMainClass();
+ File pluginFile = metadata.getFile();
+ try {
+ // copy file in a temp directory because Windows+Oracle JVM Classloader lock the JAR file
+ File tempFile = File.createTempFile(pluginFile.getName(), null);
+ FileUtils.copyFile(pluginFile, tempFile);
+
+ URLClassLoader pluginClassLoader = URLClassLoader.newInstance(new URL[]{tempFile.toURI().toURL()}, getClass().getClassLoader());
+ Plugin pluginInstance = (Plugin) pluginClassLoader.loadClass(mainClass).newInstance();
+ metadata.setKey(PluginKeyUtils.sanitize(pluginInstance.getKey()));
+ metadata.setDescription(pluginInstance.getDescription());
+ metadata.setName(pluginInstance.getName());
+
+ } catch (Exception e) {
+ throw new RuntimeException("The metadata main class can not be created. Plugin file=" + pluginFile.getName() + ", class=" + mainClass, e);
+ }
+ }
+}
+++ /dev/null
-/*
- * 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.core.plugins;
-
-import org.apache.commons.io.FileUtils;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-
-public class PluginFileExtractorTest {
-
- private PluginFileExtractor extractor= new PluginFileExtractor();
-
- @Test
- public void shouldExtractMetadata() {
- DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-checkstyle-plugin-2.8.jar"), true);
- assertThat(metadata.getKey(), is("checkstyle"));
- assertThat(metadata.getBasePlugin(), nullValue());
- assertThat(metadata.getName(), is("Checkstyle"));
- assertThat(metadata.isCore(), is(true));
- assertThat(metadata.getFile().getName(), is("sonar-checkstyle-plugin-2.8.jar"));
- }
-
- @Test
- public void shouldExtractDeprecatedMetadata() {
- DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-emma-plugin-0.3.jar"), false);
- assertThat(metadata.getKey(), is("emma"));
- assertThat(metadata.getBasePlugin(), nullValue());
- assertThat(metadata.getName(), is("Emma"));
- }
-
- @Test
- public void shouldExtractExtensionMetadata() {
- DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"), true);
- assertThat(metadata.getKey(), is("checkstyleextensions"));
- assertThat(metadata.getBasePlugin(), is("checkstyle"));
- }
-
- @Test
- public void shouldCopyAndExtractDependencies() throws IOException {
- File toDir = new File("target/test-tmp/PluginFileExtractorTest/shouldCopyAndExtractDependencies");
- FileUtils.forceMkdir(toDir);
- FileUtils.cleanDirectory(toDir);
-
- DefaultPluginMetadata metadata = extractor.install(getFile("sonar-checkstyle-plugin-2.8.jar"), true, null, toDir);
-
- assertThat(metadata.getKey(), is("checkstyle"));
- assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
- assertThat(new File(toDir, "META-INF/lib/checkstyle-5.1.jar").exists(), is(true));
- }
-
- @Test
- public void shouldExtractOnlyDependencies() throws IOException {
- File toDir = new File("target/test-tmp/PluginFileExtractorTest/shouldExtractOnlyDependencies");
- FileUtils.forceMkdir(toDir);
- FileUtils.cleanDirectory(toDir);
-
- extractor.install(getFile("sonar-checkstyle-plugin-2.8.jar"), true, null, toDir);
-
- assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
- assertThat(new File(toDir, "META-INF/MANIFEST.MF").exists(), is(false));
- assertThat(new File(toDir, "org/sonar/plugins/checkstyle/CheckstyleVersion.class").exists(), is(false));
- }
-
- @Test
- public void shouldCopyRuleExtensionsOnServerSide() throws IOException {
- File toDir = new File("target/test-tmp/PluginFileExtractorTest/shouldCopyRuleExtensionsOnServerSide");
- FileUtils.forceMkdir(toDir);
- FileUtils.cleanDirectory(toDir);
-
- DefaultPluginMetadata metadata = DefaultPluginMetadata.create(getFile("sonar-checkstyle-plugin-2.8.jar"))
- .setKey("checkstyle")
- .addDeprecatedExtension(getFile("PluginFileExtractorTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml"));
- extractor.install(metadata, toDir);
-
- assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
- assertThat(new File(toDir, "checkstyle-extension.xml").exists(), is(true));
- }
-
- private File getFile(String filename) {
- return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename));
- }
-}
--- /dev/null
+/*
+ * 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.core.plugins;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class PluginInstallerTest {
+
+ private PluginInstaller extractor= new PluginInstaller();
+
+ @Test
+ public void shouldExtractMetadata() {
+ DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-checkstyle-plugin-2.8.jar"), true);
+ assertThat(metadata.getKey(), is("checkstyle"));
+ assertThat(metadata.getBasePlugin(), nullValue());
+ assertThat(metadata.getName(), is("Checkstyle"));
+ assertThat(metadata.isCore(), is(true));
+ assertThat(metadata.getFile().getName(), is("sonar-checkstyle-plugin-2.8.jar"));
+ }
+
+ @Test
+ public void shouldExtractDeprecatedMetadata() {
+ DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-emma-plugin-0.3.jar"), false);
+ assertThat(metadata.getKey(), is("emma"));
+ assertThat(metadata.getBasePlugin(), nullValue());
+ assertThat(metadata.getName(), is("Emma"));
+ }
+
+ @Test
+ public void shouldExtractExtensionMetadata() {
+ DefaultPluginMetadata metadata = extractor.extractMetadata(getFile("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"), true);
+ assertThat(metadata.getKey(), is("checkstyleextensions"));
+ assertThat(metadata.getBasePlugin(), is("checkstyle"));
+ }
+
+ @Test
+ public void shouldCopyAndExtractDependencies() throws IOException {
+ File toDir = new File("target/test-tmp/PluginInstallerTest/shouldCopyAndExtractDependencies");
+ FileUtils.forceMkdir(toDir);
+ FileUtils.cleanDirectory(toDir);
+
+ DefaultPluginMetadata metadata = extractor.install(getFile("sonar-checkstyle-plugin-2.8.jar"), true, null, toDir);
+
+ assertThat(metadata.getKey(), is("checkstyle"));
+ assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
+ assertThat(new File(toDir, "META-INF/lib/checkstyle-5.1.jar").exists(), is(true));
+ }
+
+ @Test
+ public void shouldExtractOnlyDependencies() throws IOException {
+ File toDir = new File("target/test-tmp/PluginInstallerTest/shouldExtractOnlyDependencies");
+ FileUtils.forceMkdir(toDir);
+ FileUtils.cleanDirectory(toDir);
+
+ extractor.install(getFile("sonar-checkstyle-plugin-2.8.jar"), true, null, toDir);
+
+ assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
+ assertThat(new File(toDir, "META-INF/MANIFEST.MF").exists(), is(false));
+ assertThat(new File(toDir, "org/sonar/plugins/checkstyle/CheckstyleVersion.class").exists(), is(false));
+ }
+
+ @Test
+ public void shouldCopyRuleExtensionsOnServerSide() throws IOException {
+ File toDir = new File("target/test-tmp/PluginInstallerTest/shouldCopyRuleExtensionsOnServerSide");
+ FileUtils.forceMkdir(toDir);
+ FileUtils.cleanDirectory(toDir);
+
+ DefaultPluginMetadata metadata = DefaultPluginMetadata.create(getFile("sonar-checkstyle-plugin-2.8.jar"))
+ .setKey("checkstyle")
+ .addDeprecatedExtension(getFile("PluginInstallerTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml"));
+ extractor.install(metadata, toDir);
+
+ assertThat(new File(toDir, "sonar-checkstyle-plugin-2.8.jar").exists(), is(true));
+ assertThat(new File(toDir, "checkstyle-extension.xml").exists(), is(true));
+ }
+
+ private File getFile(String filename) {
+ return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename));
+ }
+}
+++ /dev/null
-<fake/>
\ No newline at end of file
--- /dev/null
+<fake/>
\ No newline at end of file
public interface ServerFileSystem extends ServerComponent {
File getHomeDir();
+
+ File getTempDir();
/**
* @param suffixes the file suffixes. If null, then return all the files, whatever their suffix
--- /dev/null
+/*
+ * 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.api.web;
+
+import com.google.common.annotations.Beta;
+import org.sonar.api.ServerExtension;
+
+/**
+ * Complete Ruby on Rails application (controllers/helpers/models/views)
+ * @since 2.15
+ */
+@Beta
+public abstract class RubyRailsApp implements ServerExtension {
+
+ /**
+ * The app key, ie the plugin key. It does not relate to URLs at all.
+ */
+ public abstract String getKey();
+
+ /**
+ * The classloader path to the root directory. It contains a sub-directory named app/.
+ * <p>For example if Ruby on Rails controllers are located in /org/sonar/sqale/app/controllers/,
+ * then the path is /org/sonar/sqale</p>
+ */
+ public abstract String getPath();
+
+}
public File getHomeDir() {
return homeDir;
}
+
+ public File getTempDir() {
+ return new File(homeDir, "temp");
+ }
public File getDeployDir() {
return deployDir;
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);
return pluginsByKey.get(key);
}
- public ClassLoader getClassloader(String pluginKey) {
+ public ClassLoader getClassLoader(String pluginKey) {
return classloaders.get(pluginKey);
}
public Class getClass(String pluginKey, String classname) {
Class clazz = null;
- ClassLoader classloader = getClassloader(pluginKey);
+ ClassLoader classloader = getClassLoader(pluginKey);
if (classloader != null) {
try {
clazz = classloader.loadClass(classname);
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.core.plugins.DefaultPluginMetadata;
-import org.sonar.core.plugins.PluginFileExtractor;
+import org.sonar.core.plugins.PluginInstaller;
import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.server.platform.ServerStartException;
private DefaultServerFileSystem fileSystem;
private Map<String, PluginMetadata> pluginByKeys = Maps.newHashMap();
- private PluginFileExtractor extractor;
+ private PluginInstaller extractor;
public PluginDeployer(DefaultServerFileSystem fileSystem) {
- this(fileSystem, new PluginFileExtractor());
+ this(fileSystem, new PluginInstaller());
}
- PluginDeployer(DefaultServerFileSystem fileSystem, PluginFileExtractor extractor) {
+ PluginDeployer(DefaultServerFileSystem fileSystem, PluginInstaller extractor) {
this.fileSystem = fileSystem;
this.extractor = extractor;
}
String resource = getResourcePath(request);
DefaultServerPluginRepository pluginRepository = Platform.getInstance().getContainer().getComponentByType(DefaultServerPluginRepository.class);
- ClassLoader classLoader = pluginRepository.getClassloader(pluginKey);
+ ClassLoader classLoader = pluginRepository.getClassLoader(pluginKey);
if (classLoader == null) {
LOG.error("Plugin not found: " + pluginKey);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
--- /dev/null
+/*
+ * 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<String, String>() {
+ @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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.<String>identity());
+ }
+
+ public static File copyResources(ClassLoader classLoader, String rootPath, File toDir, Function<String, String> relocationFunction) {
+ Collection<String> 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<String> listFiles(ClassLoader classLoader, String rootPath) {
+ return listResources(classLoader, rootPath, new Predicate<String>() {
+ @Override
+ public boolean apply(@Nullable String path) {
+ return !StringUtils.endsWith(path, "/");
+ }
+ });
+ }
+
+ public static Collection<String> listResources(ClassLoader classloader, String rootPath) {
+ return listResources(classloader, rootPath, Predicates.<String>alwaysTrue());
+ }
+
+ public static Collection<String> listResources(ClassLoader classloader, String rootPath, Predicate<String> predicate) {
+ try {
+ Collection<String> 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<JarEntry> 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);
+ }
+ }
+}
import org.apache.commons.configuration.Configuration;
import org.slf4j.LoggerFactory;
+import org.sonar.api.CoreProperties;
import org.sonar.api.config.License;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
-import org.sonar.api.platform.ComponentContainer;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.api.platform.*;
import org.sonar.api.profiles.ProfileExporter;
import org.sonar.api.profiles.ProfileImporter;
import org.sonar.api.resources.Language;
return License.readBase64(base64);
}
+ public String getServerHome() {
+ return getContainer().getComponentByType(Settings.class).getString(CoreProperties.SONAR_HOME);
+ }
public ReviewsNotificationManager getReviewsNotificationManager() {
return getContainer().getComponentByType(ReviewsNotificationManager.class);
require File.join(File.dirname(__FILE__), 'boot')
require 'color'
+#
+# Limitation of Rails 2.3 and Rails Engines (plugins) when threadsafe! is enabled in production mode
+# See http://groups.google.com/group/rubyonrails-core/browse_thread/thread/9067bce01444fb24?pli=1
+#
+class EagerPluginLoader < Rails::Plugin::Loader
+ def add_plugin_load_paths
+ super
+ plugins.each do |plugin|
+ if configuration.cache_classes
+ configuration.eager_load_paths += plugin.load_paths
+ end
+ end
+ end
+end
+
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# you must remove the Active Record framework.
config.frameworks -= [ :action_mailer ]
- # Add additional load paths for your own custom dirs
- # config.load_paths += %W( #{RAILS_ROOT}/extras )
+ # This property can't be set in config/environments because of execution order
+ # See http://strd6.com/2009/04/cant-dup-nilclass-maybe-try-unloadable/
+ config.reload_plugins=(RAILS_ENV == 'development')
+
+ config.plugin_loader = EagerPluginLoader
+ config.plugin_paths << "#{Java::OrgSonarServerUi::JRubyFacade.getInstance().getServerHome()}/temp/ror"
# Force all environments to use the same logger level
# (by default production uses :info, the others :debug)
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
end
+
+
class ActiveRecord::Migration
def self.alter_to_big_primary_key(tablename)
dialect = ::Java::OrgSonarServerUi::JRubyFacade.getInstance().getDatabase().getDialect().getActiveRecordDialectCode()
when "postgre"
execute "ALTER TABLE #{tablename} ALTER COLUMN id TYPE bigint"
when "mysql"
- execute "ALTER TABLE #{tablename} CHANGE id id BIGINT AUTO_INCREMENT";
+ execute "ALTER TABLE #{tablename} CHANGE id id BIGINT AUTO_INCREMENT"
when "derby"
# do nothing as alter can not do the job in Derby
when "oracle"
assertThat(repository.getPlugins().size(), Is.is(1));
assertThat(repository.getPlugin("artifactsize"), not(nullValue()));
- assertThat(repository.getClassloader("artifactsize"), not(nullValue()));
+ assertThat(repository.getClassLoader("artifactsize"), not(nullValue()));
assertThat(repository.getClass("artifactsize", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics"), not(nullValue()));
assertThat(repository.getClass("artifactsize", "org.Unknown"), nullValue());
assertThat(repository.getClass("other", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics"), nullValue());
import org.junit.Test;
import org.junit.rules.TestName;
import org.sonar.api.platform.PluginMetadata;
-import org.sonar.core.plugins.PluginFileExtractor;
+import org.sonar.core.plugins.PluginInstaller;
import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.server.platform.ServerStartException;
import org.sonar.test.TestUtils;
public class PluginDeployerTest {
- private PluginFileExtractor extractor;
+ private PluginInstaller extractor;
private DefaultServerFileSystem fileSystem;
private File homeDir;
private File deployDir;
homeDir = TestUtils.getResource(PluginDeployerTest.class, name.getMethodName());
deployDir = TestUtils.getTestTempDir(PluginDeployerTest.class, name.getMethodName() + "/deploy");
fileSystem = new DefaultServerFileSystem(null, homeDir, deployDir);
- extractor = new PluginFileExtractor();
+ extractor = new PluginInstaller();
deployer = new PluginDeployer(fileSystem, extractor);
}
--- /dev/null
+/*
+ * 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 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.api.web.RubyRailsApp;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ApplicationDeployerTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void deployRubyRailsApp() throws Exception {
+ File tempDir = this.temp.getRoot();
+ ClassLoader classLoader = new URLClassLoader(new URL[]{
+ getClass().getResource("/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp.jar").toURI().toURL()}, null);
+ ApplicationDeployer.deployRubyRailsApp(tempDir, new FakeRubyRailsApp(), 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(2));
+ 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 prepareRubyRailsRootDirectory() throws Exception {
+ ServerFileSystem fileSystem = mock(ServerFileSystem.class);
+ File tempDir = this.temp.getRoot();
+ when(fileSystem.getTempDir()).thenReturn(tempDir);
+
+ File dir = new ApplicationDeployer(fileSystem, new RubyRailsApp[]{new FakeRubyRailsApp()}).prepareRubyRailsRootDirectory();
+
+ assertThat(dir.isDirectory(), is(true));
+ assertThat(dir.exists(), is(true));
+ assertThat(dir.getCanonicalPath(), is(new File(tempDir, "ror").getCanonicalPath()));
+ }
+
+ static class FakeRubyRailsApp extends RubyRailsApp {
+
+ @Override
+ public String getKey() {
+ return "fake";
+ }
+
+ @Override
+ public String getPath() {
+ return "/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp";
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.Function;
+import com.google.common.base.Predicate;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.StringUtils;
+import org.hamcrest.core.Is;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+
+public class ClassLoaderUtilsTest {
+
+ private ClassLoader classLoader;
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Before
+ public void prepareClassLoader() {
+ // This JAR file has the three following files :
+ // org/sonar/sqale/app/copyright.txt
+ // org/sonar/sqale/app/README.md
+ // org/sonar/other/other.txt
+ URL jarUrl = getClass().getResource("/org/sonar/server/startup/ClassLoaderUtilsTest/ClassLoaderUtilsTest.jar");
+ classLoader = new URLClassLoader(new URL[]{jarUrl}, /* no parent classloader */null);
+ }
+
+ @Test
+ public void listResources_unknown_root() {
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "unknown/directory");
+ assertThat(strings.size(), Is.is(0));
+ }
+
+ @Test
+ public void listResources_all() {
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale");
+ assertThat(strings, hasItems(
+ "org/sonar/sqale/",
+ "org/sonar/sqale/app/",
+ "org/sonar/sqale/app/copyright.txt",
+ "org/sonar/sqale/app/README.md"));
+ assertThat(strings.size(), Is.is(4));
+ }
+
+ @Test
+ public void listResources_root_path_starts_with_slash() {
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "/org/sonar/sqale");
+ assertThat(strings, hasItems(
+ "org/sonar/sqale/",
+ "org/sonar/sqale/app/",
+ "org/sonar/sqale/app/copyright.txt",
+ "org/sonar/sqale/app/README.md"));
+ assertThat(strings.size(), Is.is(4));
+ }
+
+ @Test
+ public void listResources_use_predicate() {
+ Collection<String> strings = ClassLoaderUtils.listResources(classLoader, "org/sonar/sqale", new Predicate<String>() {
+ @Override
+ public boolean apply(@Nullable String s) {
+ return StringUtils.endsWith(s, "md");
+ }
+ });
+ assertThat(strings.size(), Is.is(1));
+ assertThat(strings, hasItems("org/sonar/sqale/app/README.md"));
+ }
+
+ @Test
+ public void listFiles() {
+ Collection<String> strings = ClassLoaderUtils.listFiles(classLoader, "org/sonar/sqale");
+ assertThat(strings, hasItems(
+ "org/sonar/sqale/app/copyright.txt",
+ "org/sonar/sqale/app/README.md"));
+ assertThat(strings.size(), Is.is(2));
+ }
+
+ @Test
+ public void copyRubyRailsApp() {
+ File toDir = temp.newFolder("dest");
+ ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir);
+
+ assertThat(FileUtils.listFiles(toDir, null, true).size(), Is.is(2));
+ assertThat(new File(toDir, "org/sonar/sqale/app/copyright.txt").exists(), Is.is(true));
+ assertThat(new File(toDir, "org/sonar/sqale/app/README.md").exists(), Is.is(true));
+ }
+
+ @Test
+ public void copyRubyRailsApp_relocate_files() {
+ File toDir = temp.newFolder("dest");
+ ClassLoaderUtils.copyResources(classLoader, "org/sonar/sqale", toDir, new Function<String, String>() {
+ @Override
+ public String apply(@Nullable String path) {
+ return "foo/" + FilenameUtils.getName(path);
+ }
+ });
+
+ assertThat(FileUtils.listFiles(toDir, null, true).size(), Is.is(2));
+ assertThat(new File(toDir, "foo/copyright.txt").exists(), Is.is(true));
+ assertThat(new File(toDir, "foo/README.md").exists(), Is.is(true));
+ }
+}