]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3224 API: support Ruby on Rails applications
authorSimon Brandhof <simon.brandhof@gmail.com>
Thu, 22 Mar 2012 15:49:01 +0000 (16:49 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Thu, 22 Mar 2012 18:31:59 +0000 (19:31 +0100)
24 files changed:
sonar-application/src/main/assembly/temp/README.txt [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
sonar-core/src/main/java/org/sonar/core/plugins/PluginFileExtractor.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/PluginInstaller.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/plugins/PluginFileExtractorTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/plugins/PluginInstallerTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/plugins/PluginFileExtractorTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/PluginInstallerTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/platform/ServerFileSystem.java
sonar-plugin-api/src/main/java/org/sonar/api/web/RubyRailsApp.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/plugins/DefaultServerPluginRepository.java
sonar-server/src/main/java/org/sonar/server/plugins/PluginDeployer.java
sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
sonar-server/src/main/java/org/sonar/server/startup/ApplicationDeployer.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/startup/ClassLoaderUtils.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/config/environment.rb
sonar-server/src/test/java/org/sonar/server/plugins/DefaultServerPluginRepositoryTest.java
sonar-server/src/test/java/org/sonar/server/plugins/PluginDeployerTest.java
sonar-server/src/test/java/org/sonar/server/startup/ApplicationDeployerTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/startup/ClassLoaderUtilsTest.java [new file with mode: 0644]
sonar-server/src/test/resources/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp.jar [new file with mode: 0644]

diff --git a/sonar-application/src/main/assembly/temp/README.txt b/sonar-application/src/main/assembly/temp/README.txt
new file mode 100644 (file)
index 0000000..4f829ef
--- /dev/null
@@ -0,0 +1 @@
+This directory contains temporary data required by server. Do not clean up when server is up.
\ No newline at end of file
index b95103e50d9f8e821de3cee93ad80f73ba128365..2df80b7309043b12e871619fd5872b86edfc4fbb 100644 (file)
@@ -33,7 +33,7 @@ import org.sonar.api.config.Settings;
 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;
@@ -68,7 +68,7 @@ public class BatchPluginRepository implements PluginRepository {
   }
 
   void doStart(List<RemotePlugin> remotePlugins) {
-    PluginFileExtractor extractor = new PluginFileExtractor();
+    PluginInstaller extractor = new PluginInstaller();
     metadataByKey = Maps.newHashMap();
     for (RemotePlugin remote : remotePlugins) {
       if (isAccepted(remote.getKey())) {
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginFileExtractor.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginFileExtractor.java
deleted file mode 100644 (file)
index 59676d0..0000000
+++ /dev/null
@@ -1,139 +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.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);
-    }
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginInstaller.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginInstaller.java
new file mode 100644 (file)
index 0000000..3be1816
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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);
+    }
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginFileExtractorTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginFileExtractorTest.java
deleted file mode 100644 (file)
index fbd644e..0000000
+++ /dev/null
@@ -1,105 +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.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));
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginInstallerTest.java
new file mode 100644 (file)
index 0000000..9afc9c9
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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));
+  }
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginFileExtractorTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginFileExtractorTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml
deleted file mode 100644 (file)
index 75a263d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<fake/>
\ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginInstallerTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginInstallerTest/shouldCopyRuleExtensionsOnServerSide/checkstyle-extension.xml
new file mode 100644 (file)
index 0000000..75a263d
--- /dev/null
@@ -0,0 +1 @@
+<fake/>
\ No newline at end of file
index c93e3b618841b3a01a62d7baf3abdea3672878d8..59334343bd844ad4d3f60e003588ea9c6e6a4d91 100644 (file)
@@ -30,6 +30,8 @@ import java.util.List;
 public interface ServerFileSystem extends ServerComponent {
 
   File getHomeDir();
+  
+  File getTempDir();
 
   /**
    * @param suffixes the file suffixes. If null, then return all the files, whatever their suffix
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/RubyRailsApp.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/RubyRailsApp.java
new file mode 100644 (file)
index 0000000..22c05c7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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();
+
+}
index b37b247b01ef8e82731491552ad5183a6a2f0893..17f66d2abbb1df7f69602d32c890a101edead705 100644 (file)
@@ -102,6 +102,10 @@ public class DefaultServerFileSystem implements ServerFileSystem {
   public File getHomeDir() {
     return homeDir;
   }
+  
+  public File getTempDir() {
+    return new File(homeDir, "temp");
+  }
 
   public File getDeployDir() {
     return deployDir;
index c439f93f1f2efd9caf5e9fb09c35e35955208e0b..f479bab81233397de904e58c7165a834fceea8a9 100644 (file)
@@ -220,6 +220,7 @@ 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);
index 24fec06a175676ddb1d5dc1e464db11423dd249f..7ba9f79f6ed637ed4752cd4d1cf8058758ff9e8c 100644 (file)
@@ -80,13 +80,13 @@ public class DefaultServerPluginRepository implements ServerPluginRepository {
     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);
index 26dca42e09dda4d74d9eed7f531e3134137bfb2f..d098265f9774edaebd29efcbbac2517a1236cf1d 100644 (file)
@@ -31,7 +31,7 @@ import org.sonar.api.utils.Logs;
 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;
 
@@ -47,13 +47,13 @@ public class PluginDeployer implements ServerComponent {
 
   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;
   }
index 0368b7dc9b1f1c8c37f4cdee2bb43cda97edcd51..d84970b7ae83dd373dc4ae61a965125828efdecf 100644 (file)
@@ -46,7 +46,7 @@ public class StaticResourcesServlet extends HttpServlet {
     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);
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
new file mode 100644 (file)
index 0000000..5d3165a
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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);
+    }
+  }
+}
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
new file mode 100644 (file)
index 0000000..95e95b2
--- /dev/null
@@ -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.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);
+    }
+  }
+}
index be0d7388ac390297490a864ca065217bd64aa050..aad5fefd1ae80c5ad9a85865b35cbd571b6127fa 100644 (file)
@@ -21,12 +21,11 @@ package org.sonar.server.ui;
 
 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;
@@ -416,6 +415,9 @@ public final class JRubyFacade {
     return License.readBase64(base64);
   }
 
+  public String getServerHome() {
+    return getContainer().getComponentByType(Settings.class).getString(CoreProperties.SONAR_HOME);
+  }
 
   public ReviewsNotificationManager getReviewsNotificationManager() {
     return getContainer().getComponentByType(ReviewsNotificationManager.class);
index 5d3bcbf5bc2f39858eb1cc1a52a1372d075f262e..fa5caee27e73e91bea442f30d3f2b6048ae234ac 100644 (file)
@@ -5,6 +5,21 @@
 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
@@ -15,8 +30,12 @@ Rails::Initializer.run do |config|
   # 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)
@@ -51,6 +70,8 @@ Rails::Initializer.run do |config|
   # 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()
@@ -58,7 +79,7 @@ class ActiveRecord::Migration
     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"
index 12533899cd43d8dab63f08e85ec755ed3887cd51..d053aab5074a70be2a1ade0926572f9f29231038 100644 (file)
@@ -68,7 +68,7 @@ public class DefaultServerPluginRepositoryTest {
 
     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());
index aa3cd2f53d71ebe29a5a8a343ec5880df81c0c5f..0912266584413ed15b03532573aeffa4787f1c35 100644 (file)
@@ -25,7 +25,7 @@ import org.junit.Rule;
 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;
@@ -39,7 +39,7 @@ import static org.junit.Assert.assertThat;
 
 public class PluginDeployerTest {
 
-  private PluginFileExtractor extractor;
+  private PluginInstaller extractor;
   private DefaultServerFileSystem fileSystem;
   private File homeDir;
   private File deployDir;
@@ -53,7 +53,7 @@ public class PluginDeployerTest {
     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);
   }
 
diff --git a/sonar-server/src/test/java/org/sonar/server/startup/ApplicationDeployerTest.java b/sonar-server/src/test/java/org/sonar/server/startup/ApplicationDeployerTest.java
new file mode 100644 (file)
index 0000000..7eb728e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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";
+    }
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/startup/ClassLoaderUtilsTest.java b/sonar-server/src/test/java/org/sonar/server/startup/ClassLoaderUtilsTest.java
new file mode 100644 (file)
index 0000000..1960a1c
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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));
+  }
+}
diff --git a/sonar-server/src/test/resources/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp.jar b/sonar-server/src/test/resources/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp.jar
new file mode 100644 (file)
index 0000000..e4f4dfb
Binary files /dev/null and b/sonar-server/src/test/resources/org/sonar/server/startup/ApplicationDeployerTest/FakeRubyRailsApp.jar differ