import org.sonar.server.permission.PermissionFinder;
import org.sonar.server.permission.ws.PermissionsWs;
import org.sonar.server.platform.monitoring.*;
+import org.sonar.server.ruby.PlatformRackBridge;
+import org.sonar.server.ruby.PlatformRubyBridge;
import org.sonar.server.platform.ws.*;
import org.sonar.server.plugins.*;
import org.sonar.server.plugins.ws.InstalledPluginsWsAction;
new TempFolderProvider(),
System2.INSTANCE,
+ // rack bridges
+ PlatformRackBridge.class,
+
// DB
DbClient.class,
DefaultServerUpgradeStatus.class,
DatabaseMigrator.class,
+ // depends on Ruby
+ PlatformRubyBridge.class,
+
// plugins
ServerPluginJarsInstaller.class,
ServerPluginJarInstaller.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+import org.jruby.Ruby;
+import org.jruby.rack.RackApplicationFactory;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+
+/**
+ * Implementation of {@link RackBridge} which get access to the Rack object through the {@link ServletContext}.
+ */
+public class PlatformRackBridge implements RackBridge {
+ public static final String RACK_FACTORY_ATTR_KEY = "rack.factory";
+ private final ServletContext servletContext;
+
+ public PlatformRackBridge(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ /**
+ * From {@link org.jruby.rack.RackServletContextListener#contextInitialized(ServletContextEvent)} implementation, we
+ * know that the {@link RackApplicationFactory} is stored in the {@link ServletContext} under the attribute key
+ * {@link #RACK_FACTORY_ATTR_KEY}.
+ *
+ * @return a {@link Ruby} object representing the runtime environment of the RoR application of the platform.
+ *
+ * @throws RuntimeException if the {@link RackApplicationFactory} can not be retrieved
+ */
+ @Override
+ public Ruby getRubyRuntime() {
+ Object attribute = servletContext.getAttribute(RACK_FACTORY_ATTR_KEY);
+ if (!(attribute instanceof RackApplicationFactory)) {
+ throw new RuntimeException("Can not retrieve the RackApplicationFactory from ServletContext. " +
+ "Ruby runtime can not be retrieved");
+ }
+
+ RackApplicationFactory rackApplicationFactory = (RackApplicationFactory) attribute;
+ return rackApplicationFactory.getApplication().getRuntime();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+import org.jruby.Ruby;
+import org.jruby.RubyNil;
+import org.jruby.RubyRuntimeAdapter;
+import org.jruby.embed.InvokeFailedException;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.jruby.javasupport.JavaUtil;
+import org.jruby.runtime.builtin.IRubyObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class PlatformRubyBridge implements RubyBridge {
+ private static final String CALL_UPGRADE_AND_START_RB_FILENAME = "call_upgrade_and_start.rb";
+
+ private final RackBridge rackBridge;
+ private final RubyRuntimeAdapter adapter = JavaEmbedUtils.newRuntimeAdapter();
+
+ public PlatformRubyBridge(RackBridge rackBridge) {
+ this.rackBridge = rackBridge;
+ }
+
+ @Override
+ public RubyDatabaseMigration databaseMigration() {
+ final CallUpgradeAndStart callUpgradeAndStart = parseMethodScriptToInterface(
+ CALL_UPGRADE_AND_START_RB_FILENAME, CallUpgradeAndStart.class
+ );
+
+ return new RubyDatabaseMigration() {
+ @Override
+ public void trigger() {
+ callUpgradeAndStart.callUpgradeAndStart();
+ }
+ };
+ }
+
+ /**
+ * Parses a Ruby script that defines a single method and returns an instance of the specified interface type as a
+ * wrapper to this Ruby method.
+ */
+ private <T> T parseMethodScriptToInterface(String fileName, Class<T> clazz) {
+ try (InputStream in = getClass().getResourceAsStream(fileName)) {
+ Ruby rubyRuntime = rackBridge.getRubyRuntime();
+ JavaEmbedUtils.EvalUnit evalUnit = adapter.parse(rubyRuntime, in, fileName, 0);
+ IRubyObject rubyObject = evalUnit.run();
+ Object receiver = JavaEmbedUtils.rubyToJava(rubyObject);
+ T wrapper = getInstance(rubyRuntime, receiver, clazz);
+ return wrapper;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to load script " + fileName, e);
+ }
+ }
+
+ /**
+ * Fork of method {@link org.jruby.embed.internal.EmbedRubyInterfaceAdapterImpl#getInstance(Object, Class)}
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getInstance(Ruby runtime, Object receiver, Class<T> clazz) {
+ if (clazz == null || !clazz.isInterface()) {
+ return null;
+ }
+ Object o;
+ if (receiver == null || receiver instanceof RubyNil) {
+ o = JavaEmbedUtils.rubyToJava(runtime, runtime.getTopSelf(), clazz);
+ } else if (receiver instanceof IRubyObject) {
+ o = JavaEmbedUtils.rubyToJava(runtime, (IRubyObject) receiver, clazz);
+ } else {
+ IRubyObject rubyReceiver = JavaUtil.convertJavaToRuby(runtime, receiver);
+ o = JavaEmbedUtils.rubyToJava(runtime, rubyReceiver, clazz);
+ }
+ String name = clazz.getName();
+ try {
+ Class<T> c = (Class<T>) Class.forName(name, true, o.getClass().getClassLoader());
+ return c.cast(o);
+ } catch (ClassNotFoundException e) {
+ throw new InvokeFailedException(e);
+ }
+ }
+
+ /**
+ * Interface which must be public to be used by the Ruby engine but that hides name of the Ruby method in the Ruby
+ * script from the rest of the platform (only {@link RubyDatabaseMigration} is known to the platform).
+ */
+ public interface CallUpgradeAndStart {
+
+ /**
+ * Java method that calls the upgrade_and_start method defined in the {@code call_upgrade_and_start.rb} script.
+ */
+ void callUpgradeAndStart();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+import org.jruby.Ruby;
+
+/**
+ * Acts as a bridge between the Java application of the platform and the Rack application.
+ */
+public interface RackBridge {
+ /**
+ * Provides access to {@link Ruby} runtime instance created by the Rack application.
+ * @return
+ */
+ Ruby getRubyRuntime();
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+/**
+ * This component acts as a bridge between a Ruby runtime and Java. Each method it defines creates a wrapping
+ * Java object which an underlying Ruby implementation.
+ */
+public interface RubyBridge {
+ /**
+ * Returns a wrapper class that allows calling the database migration in Ruby.
+ *
+ * @return a {@link RubyDatabaseMigration}
+ */
+ RubyDatabaseMigration databaseMigration();
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+/**
+ * Represents the database migration written in Ruby.
+ */
+public interface RubyDatabaseMigration {
+ /**
+ * Triggers the Ruby migration with ActiveRecord.
+ * <strong>This is not thread safe!</strong>
+ */
+ void trigger();
+}
--- /dev/null
+# this script defines a method which calls the class method "upgrade_and_start" of the DatabaseVersion class defined
+# in /server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb
+
+require 'database_version'
+
+def call_upgrade_and_start
+ DatabaseVersion.upgrade_and_start
+end
\ No newline at end of file
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+import org.jruby.rack.RackApplication;
+import org.jruby.rack.RackApplicationFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.ServletContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class PlatformRackBridgeTest {
+ @Mock
+ private ServletContext servletContext;
+ @Mock
+ private RackApplicationFactory rackApplicationFactory;
+ @Mock
+ private RackApplication rackApplication;
+
+ @InjectMocks
+ PlatformRackBridge underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void getRubyRuntime_throws_RE_when_RackApplicationFactory_is_not_in_ServletContext() throws Exception {
+ underTest.getRubyRuntime();
+ }
+
+ @Test
+ public void getRubyRuntime_returns_Ruby_instance_from_rack_application() throws Exception {
+ when(servletContext.getAttribute("rack.factory")).thenReturn(rackApplicationFactory);
+ when(rackApplicationFactory.getApplication()).thenReturn(rackApplication);
+
+ // since Ruby object can not be mocked and creating a Ruby instance is costly, we only make sure rackApplication#getRuntime method is
+ // called and that we get null (as rackApplication#getRuntime() returns null)
+ assertThat(underTest.getRubyRuntime()).isNull();
+ verify(rackApplication).getRuntime();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ruby;
+
+import com.google.common.collect.ImmutableList;
+import org.jruby.embed.LocalContextScope;
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.exceptions.RaiseException;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PlatformRubyBridgeTest {
+ private static ScriptingContainer container = setupScriptingContainer();
+
+ private RackBridge rackBridge = mock(RackBridge.class);
+ private PlatformRubyBridge underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ when(rackBridge.getRubyRuntime()).thenReturn(container.getProvider().getRuntime());
+ underTest = new PlatformRubyBridge(rackBridge);
+ }
+
+ /**
+ * Creates a Ruby runtime which loading path includes the test resource directory where our Ruby test DatabaseVersion
+ * is defined.
+ */
+ private static ScriptingContainer setupScriptingContainer() {
+ try {
+ ScriptingContainer container = new ScriptingContainer(LocalContextScope.CONCURRENT);
+ URL resource = PlatformRubyBridge.class.getResource("database_version.rb");
+ String dirPath = new File(resource.toURI()).getParentFile().getPath();
+ container.setLoadPaths(ImmutableList.of(dirPath));
+
+ return container;
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * unit test only makes sure the wrapping and method forwarding provided by JRuby works so building the
+ * RubyDatabaseMigration object and calling its trigger method is enough as it would otherwise raise an exception
+ */
+ @Test
+ public void testDatabaseMigration() {
+ try {
+ underTest.databaseMigration().trigger();
+ } catch (RaiseException e) {
+ throw new RuntimeException("Loading error with container loadPath " + container.getLoadPaths(), e);
+ }
+ }
+
+}
--- /dev/null
+# a dummy class DatabaseVersion that "mocks" the DatabaseVersion class of the platform by defining a class method called upgrade_and_start
+# unit test only makes sure the wrapping and method forwarding provided by JRuby works so providing an empty method is enough as
+# it would otherwise raise an exception
+class DatabaseVersion
+
+ def self.upgrade_and_start
+
+ end
+
+end