Stefan Saasen avatar Stefan Saasen committed 3a4d9fa

Make Ruby scripts reloadable in dev mode.

Comments (0)

Files changed (3)

             <resource>
                 <filtering>false</filtering>
                 <directory>src/main/rubygems/gems</directory>
-                <targetPath>rubygems</targetPath>
+                <targetPath>rubygems/gems</targetPath>
                 <includes>
                     <include>*/lib/**/*</include>
                 </includes>

src/main/java/com/atlassian/plugins/polyglot/jrubyexample/bridge/ScriptingContainerProviderImpl.java

 package com.atlassian.plugins.polyglot.jrubyexample.bridge;
 
+import static org.jruby.RubyInstanceConfig.CompileMode;
+
 import com.atlassian.confluence.core.ConfluenceSystemProperties;
 import com.atlassian.util.concurrent.LazyReference;
 import com.atlassian.util.concurrent.Supplier;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Nullable;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
-import static org.jruby.RubyInstanceConfig.CompileMode;
-
 @Component
 public class ScriptingContainerProviderImpl implements ScriptingContainerProvider, DisposableBean {
 
             ScriptingContainer container = new ScriptingContainer(LocalContextScope.CONCURRENT,
                     LocalVariableBehavior.TRANSIENT);
             container.setHomeDirectory("classpath:/META-INF/jruby.home");
-            container.setCompileMode(ConfluenceSystemProperties.isDevMode() ?
+
+            final boolean isDevMode = ConfluenceSystemProperties.isDevMode();
+            container.setCompileMode(isDevMode ?
                     CompileMode.OFF :
                     CompileMode.JIT);
             container.setKCode(KCode.UTF8);
 
             // Define Ruby LOAD_PATH and add the Rubygems lib/ directories to it
-            List<String> gemLoadPaths = resolveGemLoadPaths();
             List<String> loadPaths = new ArrayList<String>(container.getLoadPaths());
-            loadPaths.addAll(gemLoadPaths);
+            loadPaths.addAll(createLoadPaths(isDevMode));
             container.setLoadPaths(loadPaths);
             return container;
         }
     };
 
+    private List<String> createLoadPaths(boolean isDevMode) {
+        List<String> loadPaths = new ArrayList<String>();
+        PathTransformer transformer = new ClassPathTransformer();
+        if (isDevMode) {
+            ResourcePathProvider resourcePathProvider = new ResourcePathProvider();
+            transformer = new ResourceDirectoryTransformer(resourcePathProvider);
+            // Add the ruby resource directory
+            loadPaths.add(resourcePathProvider.getRubySourcesPath());
+        }
+        List<String> gemLoadPaths = resolveGemLoadPaths(transformer);
+        loadPaths.addAll(gemLoadPaths);
+        return loadPaths;
+    }
+
     @Override
     public ScriptingContainer getScriptingContainer() {
         return engine.get();
     }
 
-    private List<String> resolveGemLoadPaths() {
-        return Lists.transform(resolveGemlist(), new Function<String, String>() {
-            @Override
-            public String apply(@Nullable String s) {
-                if (StringUtils.isNotEmpty(s)) {
-                    return "classpath:/rubygems/" + s + "/lib";
-                }
-                return null;
+    private List<String> resolveGemLoadPaths(PathTransformer pathTransformer) {
+        return Lists.transform(resolveGemlist(), pathTransformer);
+    }
+
+    @VisibleForTesting
+    static class ResourcePathProvider {
+
+        // We piggy back on the resource directories as used by the plugin framework
+        private static final String PLUGIN_RESOURCE_DIRECTORIES = "plugin.resource.directories";
+
+        private File rubySource;
+        private File rubyGemsSource;
+
+        ResourcePathProvider() {
+            this(System.getProperty(PLUGIN_RESOURCE_DIRECTORIES));
+        }
+
+        ResourcePathProvider(String dirs) {
+            if (StringUtils.isNotBlank(dirs)) {
+                List<File> directories = Lists.transform(Arrays.asList(dirs.split(",")), new Function<String, File>() {
+                    @Override public File apply(@Nullable String input) {
+                        File file = new File(input);
+                        if (file.exists()) {
+                            return file;
+                        }
+                        return null;
+                    }
+                });
+
+                rubySource = Iterables.find(directories, new Predicate<File>() {
+                    @Override public boolean apply(@Nullable File input) {
+                        return null != input && input.getAbsolutePath().endsWith("/ruby");
+                    }
+                }, null);
+
+                rubyGemsSource = Iterables.find(directories, new Predicate<File>() {
+                    @Override public boolean apply(@Nullable File input) {
+                        return null != input && input.getAbsolutePath().endsWith("/rubygems/gems");
+                    }
+                }, null);
             }
-        });
+        }
+
+        String getRubySourcesPath() {
+            return null != rubySource ? rubySource.getAbsolutePath() : null;
+        }
+
+        String getRubyGemsSourcesPath() {
+            return null != rubyGemsSource ? rubyGemsSource.getAbsolutePath() : null;
+        }
+    }
+
+    private static interface PathTransformer extends Function<String, String> {
+    }
+
+    private static class ResourceDirectoryTransformer implements PathTransformer {
+        private final String rubyGemsPath;
+
+        private ResourceDirectoryTransformer(ResourcePathProvider resourcePathProvider) {
+            rubyGemsPath = resourcePathProvider.getRubyGemsSourcesPath();
+        }
+
+        @Override
+        public String apply(@Nullable String s) {
+            if (StringUtils.isNotEmpty(s)) {
+                return String.format("%s/%s/lib", rubyGemsPath, s);
+            }
+            return null;
+        }
+    }
+
+    private static class ClassPathTransformer implements PathTransformer {
+        @Override
+        public String apply(@Nullable String s) {
+            if (StringUtils.isNotEmpty(s)) {
+                return "classpath:/rubygems/gems/" + s + "/lib";
+            }
+            return null;
+        }
     }
 
     private List<String> resolveGemlist() {
         InputStream is = getClass().getClassLoader().getResourceAsStream("/META-INF/gemspec.index");
         try {
             return IOUtils.readLines(is);
-        } catch (IOException e) {
+        }
+        catch (IOException e) {
             return Collections.emptyList();
-        } finally {
+        }
+        finally {
             IOUtils.closeQuietly(is);
         }
     }

src/test/java/com/atlassian/plugins/polyglot/jrubyexample/bridge/ScriptingContainerProviderTest.java

+package com.atlassian.plugins.polyglot.jrubyexample.bridge;
+
+import static com.atlassian.plugins.polyglot.jrubyexample.bridge.ScriptingContainerProviderImpl.ResourcePathProvider;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class ScriptingContainerProviderTest {
+
+    private static final String EXAMPLE_PATH = "/Users/ssaasen/dev/atlassian/plugins/atlassian-jruby-example-plugin/src/main/ruby,/Users/ssaasen/dev/atlassian/plugins/atlassian-jruby-example-plugin/src/main/rubygems/gems,/Users/ssaasen/dev/atlassian/plugins/atlassian-jruby-example-plugin/src/main/resources";
+    
+    @Test
+    public void testGetScriptingContainer() throws Exception {
+
+    }
+
+
+    @Test
+    public void testLocalResourceProviderEmptyString() {
+        ResourcePathProvider resourcePathProvider = new ResourcePathProvider("");
+        assertNull(resourcePathProvider.getRubyGemsSourcesPath());
+        assertNull(resourcePathProvider.getRubySourcesPath());
+    }
+
+    @Test
+    public void testLocalResourceProviderGemsPath() {
+        ResourcePathProvider resourcePathProvider = new ResourcePathProvider(EXAMPLE_PATH);
+        assertEquals("/Users/ssaasen/dev/atlassian/plugins/atlassian-jruby-example-plugin/src/main/rubygems/gems", resourcePathProvider.getRubyGemsSourcesPath());
+    }
+
+    @Test
+    public void testLocalResourceProviderSourcePath() {
+        ResourcePathProvider resourcePathProvider = new ResourcePathProvider(EXAMPLE_PATH);
+        assertEquals("/Users/ssaasen/dev/atlassian/plugins/atlassian-jruby-example-plugin/src/main/ruby", resourcePathProvider.getRubySourcesPath());
+    }
+
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.