Don Willis avatar Don Willis committed 1cfe76d

SOY-8 Code clean-up

- Extracted the GlobalFunctionModule from the DefaultSoyManager and wrote a test for it.
- Clarified what a lot of the existing code actually does.

Comments (0)

Files changed (10)

soy-template-plugin/src/main/java/com/atlassian/soy/impl/DefaultSoyManager.java

 import com.atlassian.plugin.webresource.WebResourceManager;
 import com.atlassian.sal.api.ApplicationProperties;
 import com.atlassian.sal.api.message.I18nResolver;
-import com.atlassian.soy.renderer.SoyClientFunction;
 import com.atlassian.soy.renderer.SoyException;
-import com.atlassian.soy.renderer.SoyFunctionModuleDescriptor;
-import com.atlassian.soy.renderer.SoyServerFunction;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.MapMaker;
      * Compiled Soy templates keyed on complete plugin-module key.
      */
     private ConcurrentMap<String, SoyTofu> soyTofuCache;
-    private final FunctionModuleDataFactory functionModuleDataFactory;
+    private final ModuleDataFactory moduleDataFactory;
     private final SoyDependencyInjectorFactory soyDependencyInjectorFactory;
 
     public DefaultSoyManager(PluginAccessor pluginAccessor, PluginEventManager pluginEventManager,
         });
 
         soyDependencyInjectorFactory = new SoyDependencyInjectorFactory(defaultModules, soyDataConverter, INJECTOR_CACHE_EXPIRY_TIME, INJECTOR_CACHE_EXPIRY_TIME_UNIT);
-        functionModuleDataFactory = new FunctionModuleDataFactory(pluginAccessor, servletContextFactory, INJECTOR_CACHE_EXPIRY_TIME, INJECTOR_CACHE_EXPIRY_TIME_UNIT);
+        moduleDataFactory = new ModuleDataFactory(pluginAccessor, servletContextFactory, INJECTOR_CACHE_EXPIRY_TIME, INJECTOR_CACHE_EXPIRY_TIME_UNIT);
         pluginEventManager.register(this);
     }
 
     @Override
-    @Deprecated
-    public SoyFileSet.Builder makeBuilder(Module... additionalModules)
-    {
-        // no caching :(
-        @SuppressWarnings("deprecation") // these two deprecated methods should be removed simultaneously
-        Injector injector = soyDependencyInjectorFactory.makeInjector(additionalModules);
-        return injector.getInstance(SoyFileSet.Builder.class);
-    }
-
-    @Override
     public void render(Appendable appendable, String completeModuleKey, String templateName, Map<String, Object> data, Map<String, Object> injectedData) throws SoyException
     {
         if (isDevMode())
     {
         soyTofuCache.clear();
         soyDependencyInjectorFactory.clear();
-        functionModuleDataFactory.clear();
+        moduleDataFactory.clear();
     }
 
     /*
     public SoyFileSet.Builder makeBuilder(String... functionModuleKeys)
     {
         List<String> keyList = ImmutableList.copyOf(functionModuleKeys);
-        FunctionModuleData data = functionModuleDataFactory.get(keyList);
+        ModuleData data = moduleDataFactory.get(keyList);
         Injector injector = soyDependencyInjectorFactory.get(data);
         SoyFileSet.Builder builder = injector.getInstance(SoyFileSet.Builder.class);
         for (URL file : data.getFileSet())
         }
     }
 
-    private static class GlobalFunctionsModule extends AbstractModule
-    {
-        private final SoyDataConverter soyDataConverter;
-        private final PluginAccessor pluginAccessor;
-
-        public GlobalFunctionsModule(SoyDataConverter soyDataConverter, PluginAccessor pluginAccessor)
-        {
-            this.soyDataConverter = soyDataConverter;
-
-            this.pluginAccessor = pluginAccessor;
-        }
-
-        @Override
-        public void configure()
-        {
-            Multibinder<SoyFunction> binder = Multibinder.newSetBinder(binder(), SoyFunction.class);
-            for (SoyFunctionModuleDescriptor md : pluginAccessor.getEnabledModuleDescriptorsByClass(SoyFunctionModuleDescriptor.class))
-            {
-                final Object function = md.getModule();
-                if (function instanceof SoyServerFunction)
-                {
-                    if (function instanceof SoyClientFunction)
-                    {
-                        binder.addBinding().toInstance(new CompositeFunctionAdaptor(function, soyDataConverter));
-                    }
-                    else
-                    {
-                        binder.addBinding().toInstance(new SoyTofuFunctionAdapter((SoyServerFunction) function, soyDataConverter));
-                    }
-                }
-                else if (function instanceof SoyClientFunction)
-                {
-                    binder.addBinding().toInstance(new SoyJsSrcFunctionAdapter((SoyClientFunction) function));
-                }
-            }
-        }
-    }
-
     private static class StackingScopeModule extends AbstractModule
     {
         @Override

soy-template-plugin/src/main/java/com/atlassian/soy/impl/FunctionModuleData.java

-package com.atlassian.soy.impl;
-
-import com.atlassian.soy.renderer.SoyServerFunction;
-import com.google.common.collect.ImmutableSet;
-import com.google.template.soy.shared.restricted.SoyFunction;
-
-import java.net.URL;
-import java.util.Set;
-
-/**
- * Cacheable collection of data associated with a set of function module keys.
- */
-final class FunctionModuleData
-{
-    private final Set<SoyServerFunction<?>> wrappedFunctions;
-    private final Set<SoyFunction> soyFunctions;
-    private final Set<URL> fileSet;
-
-    public FunctionModuleData(Set<SoyServerFunction<?>> wrappedFunctions, Set<SoyFunction> soyFunctions,
-                              Set<URL> fileSet)
-    {
-        this.wrappedFunctions = ImmutableSet.copyOf(wrappedFunctions);
-        this.soyFunctions = ImmutableSet.copyOf(soyFunctions);
-        this.fileSet = ImmutableSet.copyOf(fileSet);
-    }
-
-    public Set<SoyServerFunction<?>> getWrappedFunctions()
-    {
-        return wrappedFunctions;
-    }
-
-    public Set<SoyFunction> getSoyFunctions()
-    {
-        return soyFunctions;
-    }
-
-    public Set<URL> getFileSet()
-    {
-        return fileSet;
-    }
-
-    @Override
-    public String toString()
-    {
-        return "FunctionModuleData{" +
-            "wrappedFunctions=" + wrappedFunctions +
-            ", soyFunctions=" + soyFunctions +
-            ", fileSet=" + fileSet +
-            '}';
-    }
-
-    @Override
-    public boolean equals(Object o)
-    {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        FunctionModuleData that = (FunctionModuleData) o;
-
-        if (!fileSet.equals(that.fileSet)) return false;
-        if (!soyFunctions.equals(that.soyFunctions)) return false;
-        if (!wrappedFunctions.equals(that.wrappedFunctions)) return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode()
-    {
-        int result = wrappedFunctions.hashCode();
-        result = 31 * result + soyFunctions.hashCode();
-        result = 31 * result + fileSet.hashCode();
-        return result;
-    }
-}

soy-template-plugin/src/main/java/com/atlassian/soy/impl/FunctionModuleDataFactory.java

-package com.atlassian.soy.impl;
-
-import com.atlassian.plugin.ModuleDescriptor;
-import com.atlassian.plugin.PluginAccessor;
-import com.atlassian.plugin.elements.ResourceDescriptor;
-import com.atlassian.plugin.servlet.ServletContextFactory;
-import com.atlassian.plugin.webresource.WebResourceModuleDescriptor;
-import com.atlassian.soy.renderer.SoyResourceModuleDescriptor;
-import com.atlassian.soy.renderer.SoyServerFunction;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.MapMaker;
-import com.google.common.collect.Sets;
-import com.google.template.soy.shared.restricted.SoyFunction;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Determines and caches the appropriate {@link FunctionModuleData} for different sets of plugin modules.
- */
-class FunctionModuleDataFactory
-{
-    private static final Logger log = LoggerFactory.getLogger(FunctionModuleDataFactory.class);
-
-    private final PluginAccessor pluginAccessor;
-    private final ServletContextFactory servletContextFactory;
-
-    /**
-     * Requisite config so we can make an Injector and a Builder without parsing all the modules twice.
-     */
-    private final ConcurrentMap<List<String>, FunctionModuleData> functionModuleDataCache;
-
-    FunctionModuleDataFactory(PluginAccessor pluginAccessor, ServletContextFactory servletContextFactory,
-                              int injectorCacheExpiryTime, TimeUnit injectorCacheExpiryTimeUnit)
-    {
-        this.pluginAccessor = pluginAccessor;
-        this.servletContextFactory = servletContextFactory;
-        Function<List<String>, FunctionModuleData> dataProducer = new Function<List<String>, FunctionModuleData>()
-        {
-            @Override
-            public FunctionModuleData apply(List<String> functionModuleKeys)
-            {
-                return extractFunctionModuleData(functionModuleKeys);
-            }
-        };
-
-        functionModuleDataCache = new MapMaker()
-            .expiration(injectorCacheExpiryTime, injectorCacheExpiryTimeUnit)
-            .makeComputingMap(dataProducer);
-
-    }
-
-    /**
-     * Retrieve the {@link FunctionModuleData} applicable to a given set of plugin module keys.  ModuleData can be
-     * extracted from the SoyFunctions _within_ SoyWebResources and from SoyWebResources and WebResources that _depend_
-     * on those SoyWebResources.
-     */
-    FunctionModuleData get(Iterable<String> pluginModulesKeys)
-    {
-        return functionModuleDataCache.get(ImmutableList.copyOf(pluginModulesKeys));
-    }
-
-    void clear()
-    {
-        functionModuleDataCache.clear();
-    }
-
-
-    private FunctionModuleData extractFunctionModuleData(Iterable<String> pluginModuleKeys)
-    {
-        log.debug("Extracting Function Module Metadata for '{}'", pluginModuleKeys);
-        FunctionModuleDataBuilder functionModuleDataBuilder = new FunctionModuleDataBuilder();
-
-        for (String pluginModuleKey : pluginModuleKeys)
-        {
-            functionModuleDataBuilder.addSoyResourceForModuleKey(pluginModuleKey);
-        }
-        FunctionModuleData result = functionModuleDataBuilder.build();
-        log.debug("Extracted Function Module Metadata for '{}' was {}", pluginModuleKeys, result);
-        return result;
-    }
-
-    private class FunctionModuleDataBuilder
-    {
-        private final Set<SoyServerFunction<?>> wrappedFunctions = Sets.newHashSet();
-        private final Set<SoyFunction> soyFunctions = Sets.newHashSet();
-        private final Set<URL> fileSet = Sets.newHashSet();
-        private final Set<String> alreadyAddedModules = Sets.newHashSet();
-
-        private void addSoyResourceForModuleKey(String completeModuleKey)
-        {
-            if (alreadyAddedModules.contains(completeModuleKey))
-                return;
-
-            ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(completeModuleKey);
-
-            if (moduleDescriptor == null)
-                throw new IllegalStateException("Required plugin module " + completeModuleKey + " was either missing or disabled");
-
-            if (moduleDescriptor instanceof WebResourceModuleDescriptor)
-                addResourcesForWebResourceModule((WebResourceModuleDescriptor) moduleDescriptor);
-
-            addSoyTemplateResources(fileSet, moduleDescriptor);
-
-            if (moduleDescriptor instanceof SoyResourceModuleDescriptor)
-                addFunctionsFromSoyModuleDescriptor((SoyResourceModuleDescriptor) moduleDescriptor);
-
-            alreadyAddedModules.add(completeModuleKey);
-        }
-
-        private void addResourcesForWebResourceModule(WebResourceModuleDescriptor webResourceModuleDescriptor)
-        {
-            for (String dependencyModuleKey : webResourceModuleDescriptor.getDependencies())
-            {
-                addSoyResourceForModuleKey(dependencyModuleKey);
-            }
-        }
-
-        private void addFunctionsFromSoyModuleDescriptor(SoyResourceModuleDescriptor soyModuleDescriptor)
-        {
-
-            for (SoyServerFunction<?> function : soyModuleDescriptor.getFunctions())
-            {
-                wrappedFunctions.add(function);
-            }
-
-            for (Object nativeFunction : soyModuleDescriptor.getNativeFunctions())
-            {
-                if (nativeFunction instanceof SoyFunction)
-                    soyFunctions.add((SoyFunction) nativeFunction);
-                else
-                    log.error("Could not load Soy function " + nativeFunction + " because it is not a compatible class");
-            }
-        }
-
-        private void addSoyTemplateResources(Set<URL> fileSet, ModuleDescriptor<?> moduleDescriptor)
-        {
-            for (ResourceDescriptor resource : moduleDescriptor.getResourceDescriptors())
-            {
-                if (isSoyTemplate(resource))
-                {
-                    URL url = getSoyResourceURL(moduleDescriptor, resource);
-                    if (url != null)
-                    {
-                        fileSet.add(url);
-                    }
-                }
-            }
-        }
-
-        private boolean isSoyTemplate(ResourceDescriptor resource)
-        {
-            return resource.getLocation().endsWith(".soy");
-        }
-
-        private URL getSoyResourceURL(ModuleDescriptor moduleDescriptor, ResourceDescriptor resource)
-        {
-            final String sourceParam = resource.getParameter("source");
-            if ("webContextStatic".equalsIgnoreCase(sourceParam))
-            {
-                try
-                {
-                    return servletContextFactory.getServletContext().getResource(resource.getLocation());
-                }
-                catch (MalformedURLException e)
-                {
-                    log.error("Ignoring soy resource. Could not locate soy with location: " + resource.getLocation());
-                    return null;
-                }
-            }
-            return moduleDescriptor.getPlugin().getResource(resource.getLocation());
-        }
-
-        private FunctionModuleData build()
-        {
-            return new FunctionModuleData(wrappedFunctions, soyFunctions, fileSet);
-        }
-    }
-}

soy-template-plugin/src/main/java/com/atlassian/soy/impl/GlobalFunctionsModule.java

+package com.atlassian.soy.impl;
+
+import com.atlassian.plugin.PluginAccessor;
+import com.atlassian.soy.renderer.SoyClientFunction;
+import com.atlassian.soy.renderer.SoyFunctionModuleDescriptor;
+import com.atlassian.soy.renderer.SoyServerFunction;
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.google.template.soy.shared.restricted.SoyFunction;
+
+/**
+ * Loads functions from a pluginAccessor.
+ */
+class GlobalFunctionsModule extends AbstractModule
+{
+    private final SoyDataConverter soyDataConverter;
+    private final PluginAccessor pluginAccessor;
+
+    public GlobalFunctionsModule(SoyDataConverter soyDataConverter, PluginAccessor pluginAccessor)
+    {
+        this.soyDataConverter = soyDataConverter;
+        this.pluginAccessor = pluginAccessor;
+    }
+
+    @Override
+    public void configure()
+    {
+        Multibinder<SoyFunction> binder = Multibinder.newSetBinder(binder(), SoyFunction.class);
+        for (SoyFunctionModuleDescriptor md : pluginAccessor.getEnabledModuleDescriptorsByClass(SoyFunctionModuleDescriptor.class))
+        {
+            final Object function = md.getModule();
+            if (function instanceof SoyServerFunction)
+            {
+                if (function instanceof SoyClientFunction)
+                {
+                    binder.addBinding().toInstance(new CompositeFunctionAdaptor(function, soyDataConverter));
+                }
+                else
+                {
+                    binder.addBinding().toInstance(new SoyTofuFunctionAdapter((SoyServerFunction) function, soyDataConverter));
+                }
+            }
+            else if (function instanceof SoyClientFunction)
+            {
+                binder.addBinding().toInstance(new SoyJsSrcFunctionAdapter((SoyClientFunction) function));
+            }
+        }
+    }
+}

soy-template-plugin/src/main/java/com/atlassian/soy/impl/ModuleData.java

+package com.atlassian.soy.impl;
+
+import com.atlassian.soy.renderer.SoyServerFunction;
+import com.google.common.collect.ImmutableSet;
+import com.google.template.soy.shared.restricted.SoyFunction;
+
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * Cacheable collection of data associated with a set of resource module keys.
+ */
+final class ModuleData
+{
+    private final Set<SoyServerFunction<?>> wrappedFunctions;
+    private final Set<SoyFunction> soyFunctions;
+    private final Set<URL> fileSet;
+
+    public ModuleData(Set<SoyServerFunction<?>> wrappedFunctions, Set<SoyFunction> soyFunctions,
+                      Set<URL> fileSet)
+    {
+        this.wrappedFunctions = ImmutableSet.copyOf(wrappedFunctions);
+        this.soyFunctions = ImmutableSet.copyOf(soyFunctions);
+        this.fileSet = ImmutableSet.copyOf(fileSet);
+    }
+
+    public Set<SoyServerFunction<?>> getWrappedFunctions()
+    {
+        return wrappedFunctions;
+    }
+
+    public Set<SoyFunction> getSoyFunctions()
+    {
+        return soyFunctions;
+    }
+
+    /**
+     * Soy template files accessible through the modules
+     */
+    public Set<URL> getFileSet()
+    {
+        return fileSet;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "FunctionModuleData{" +
+            "wrappedFunctions=" + wrappedFunctions +
+            ", soyFunctions=" + soyFunctions +
+            ", fileSet=" + fileSet +
+            '}';
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ModuleData that = (ModuleData) o;
+
+        if (!fileSet.equals(that.fileSet)) return false;
+        if (!soyFunctions.equals(that.soyFunctions)) return false;
+        if (!wrappedFunctions.equals(that.wrappedFunctions)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = wrappedFunctions.hashCode();
+        result = 31 * result + soyFunctions.hashCode();
+        result = 31 * result + fileSet.hashCode();
+        return result;
+    }
+}

soy-template-plugin/src/main/java/com/atlassian/soy/impl/ModuleDataFactory.java

+package com.atlassian.soy.impl;
+
+import com.atlassian.plugin.ModuleDescriptor;
+import com.atlassian.plugin.PluginAccessor;
+import com.atlassian.plugin.elements.ResourceDescriptor;
+import com.atlassian.plugin.servlet.ServletContextFactory;
+import com.atlassian.plugin.webresource.WebResourceModuleDescriptor;
+import com.atlassian.soy.renderer.SoyResourceModuleDescriptor;
+import com.atlassian.soy.renderer.SoyServerFunction;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.MapMaker;
+import com.google.common.collect.Sets;
+import com.google.template.soy.shared.restricted.SoyFunction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Determines and caches the appropriate {@link ModuleData} for different sets of plugin modules.
+ */
+class ModuleDataFactory
+{
+    private static final Logger log = LoggerFactory.getLogger(ModuleDataFactory.class);
+
+    private final PluginAccessor pluginAccessor;
+    private final ServletContextFactory servletContextFactory;
+
+    /**
+     * Requisite config so we can make an Injector and a Builder without parsing all the modules twice.
+     */
+    private final ConcurrentMap<List<String>, ModuleData> moduleDataCache;
+
+    ModuleDataFactory(PluginAccessor pluginAccessor, ServletContextFactory servletContextFactory,
+                      int injectorCacheExpiryTime, TimeUnit injectorCacheExpiryTimeUnit)
+    {
+        this.pluginAccessor = pluginAccessor;
+        this.servletContextFactory = servletContextFactory;
+        Function<List<String>, ModuleData> dataProducer = new Function<List<String>, ModuleData>()
+        {
+            @Override
+            public ModuleData apply(List<String> moduleKeys)
+            {
+                return extractModuleData(moduleKeys);
+            }
+        };
+
+        moduleDataCache = new MapMaker()
+            .expiration(injectorCacheExpiryTime, injectorCacheExpiryTimeUnit)
+            .makeComputingMap(dataProducer);
+
+    }
+
+    /**
+     * Retrieve the {@link ModuleData} applicable to a given set of plugin module keys.  ModuleData includes template
+     * files and function sets. Functions can be extracted from the SoyFunctions _within_ SoyWebResources and from
+     * SoyWebResources and WebResources that _depend_ on those SoyWebResources. Templates can be extracted from
+     * WebResources and SoyWebResources in the dependency trees of any of the given plugin keys.
+     */
+    ModuleData get(Iterable<String> pluginModulesKeys)
+    {
+        return moduleDataCache.get(ImmutableList.copyOf(pluginModulesKeys));
+    }
+
+    void clear()
+    {
+        moduleDataCache.clear();
+    }
+
+
+    private ModuleData extractModuleData(Iterable<String> pluginModuleKeys)
+    {
+        log.debug("Extracting Soy Module Data for '{}'", pluginModuleKeys);
+        ModuleDataBuilder moduleDataBuilder = new ModuleDataBuilder();
+
+        for (String pluginModuleKey : pluginModuleKeys)
+        {
+            moduleDataBuilder.addTemplatesAndFunctionsForTree(pluginModuleKey);
+        }
+        ModuleData result = moduleDataBuilder.build();
+        log.debug("Extracted Soy Module Data for '{}' was {}", pluginModuleKeys, result);
+        return result;
+    }
+
+    private class ModuleDataBuilder
+    {
+        private final Set<SoyServerFunction<?>> wrappedFunctions = Sets.newHashSet();
+        private final Set<SoyFunction> soyFunctions = Sets.newHashSet();
+        private final Set<URL> fileSet = Sets.newHashSet();
+        private final Set<String> alreadyAddedModules = Sets.newHashSet();
+
+        private void addTemplatesAndFunctionsForTree(String completeModuleKey)
+        {
+            if (alreadyAddedModules.contains(completeModuleKey))
+                return;
+
+            ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(completeModuleKey);
+
+            if (moduleDescriptor == null)
+                throw new IllegalStateException("Required plugin module " + completeModuleKey + " was either missing or disabled");
+
+            if (moduleDescriptor instanceof WebResourceModuleDescriptor)
+                addTemplatesAndFunctionsForTree((WebResourceModuleDescriptor) moduleDescriptor);
+
+            addSoyTemplateResources(moduleDescriptor);
+
+            if (moduleDescriptor instanceof SoyResourceModuleDescriptor)
+                addFunctionsFromSoyModuleDescriptor((SoyResourceModuleDescriptor) moduleDescriptor);
+
+            alreadyAddedModules.add(completeModuleKey);
+        }
+
+        private void addTemplatesAndFunctionsForTree(WebResourceModuleDescriptor webResourceModuleDescriptor)
+        {
+            for (String dependencyModuleKey : webResourceModuleDescriptor.getDependencies())
+            {
+                addTemplatesAndFunctionsForTree(dependencyModuleKey);
+            }
+        }
+
+        private void addFunctionsFromSoyModuleDescriptor(SoyResourceModuleDescriptor soyModuleDescriptor)
+        {
+
+            for (SoyServerFunction<?> function : soyModuleDescriptor.getFunctions())
+            {
+                wrappedFunctions.add(function);
+            }
+
+            for (Object nativeFunction : soyModuleDescriptor.getNativeFunctions())
+            {
+                if (nativeFunction instanceof SoyFunction)
+                    soyFunctions.add((SoyFunction) nativeFunction);
+                else
+                    log.error("Could not load Soy function {} because it is not a compatible class", nativeFunction);
+            }
+        }
+
+        private void addSoyTemplateResources(ModuleDescriptor<?> moduleDescriptor)
+        {
+            for (ResourceDescriptor resource : moduleDescriptor.getResourceDescriptors())
+            {
+                if (isSoyTemplate(resource))
+                {
+                    URL url = getSoyResourceURL(moduleDescriptor, resource);
+                    if (url != null)
+                    {
+                        this.fileSet.add(url);
+                    }
+                }
+            }
+        }
+
+        private boolean isSoyTemplate(ResourceDescriptor resource)
+        {
+            return resource.getLocation().endsWith(".soy");
+        }
+
+        private URL getSoyResourceURL(ModuleDescriptor moduleDescriptor, ResourceDescriptor resource)
+        {
+            final String sourceParam = resource.getParameter("source");
+            if ("webContextStatic".equalsIgnoreCase(sourceParam))
+            {
+                try
+                {
+                    return servletContextFactory.getServletContext().getResource(resource.getLocation());
+                }
+                catch (MalformedURLException e)
+                {
+                    log.error("Ignoring soy resource. Could not locate soy with location: " + resource.getLocation());
+                    return null;
+                }
+            }
+            return moduleDescriptor.getPlugin().getResource(resource.getLocation());
+        }
+
+        private ModuleData build()
+        {
+            return new ModuleData(wrappedFunctions, soyFunctions, fileSet);
+        }
+    }
+}

soy-template-plugin/src/main/java/com/atlassian/soy/impl/SoyDependencyInjectorFactory.java

     /**
      * Injectors are relatively expensive and synchronous to create. Don't do it unless you have to.
      */
-    private final ConcurrentMap<FunctionModuleData, Injector> injectorCache;
+    private final ConcurrentMap<ModuleData, Injector> injectorCache;
     private SoyDataConverter soyDataConverter;
 
     public SoyDependencyInjectorFactory(Iterable<Module> defaultModules, SoyDataConverter soyDataConverter, int injectorCacheExpiryTime, TimeUnit injectorCacheExpiryTimeUnit)
         this.defaultModules = defaultModules;
         this.soyDataConverter = soyDataConverter;
         this.injectorCache = new MapMaker().expiration(injectorCacheExpiryTime, injectorCacheExpiryTimeUnit).makeComputingMap(
-                new Function<FunctionModuleData, Injector>()
+                new Function<ModuleData, Injector>()
         {
             @Override
-            public Injector apply(FunctionModuleData functionModuleData)
+            public Injector apply(ModuleData functionModuleData)
             {
                 return makeInjectorForKeys(functionModuleData, SoyDependencyInjectorFactory.this.soyDataConverter);
             }
 
     }
 
-    private Injector makeInjectorForKeys(FunctionModuleData data, SoyDataConverter soyDataConverter)
+    private Injector makeInjectorForKeys(ModuleData data, SoyDataConverter soyDataConverter)
     {
         Injector defaultInjector = defaultInjectorRef.get();
         if (data.getSoyFunctions().isEmpty() && data.getWrappedFunctions().isEmpty())
         return Guice.createInjector(modules);
     }
 
-    /**
-     * Required by the deprecated makeInjector method on DefaultSoyManager.  Shouldn't be used otherwise.
-     * Only returns a the cached defaultInjector if there are no modules passed, otherwise creates a new injector.
-     *
-     * @deprecated since 1.1.8 use {@link #get}.
-     */
-    @Deprecated
-    Injector makeInjector(Module... additionalModules)
-    {
-        return additionalModules.length == 0 ? defaultInjectorRef.get() : createInjectorForModules(additionalModules);
-    }
-
     public void clear()
     {
         defaultInjectorRef.reset();
         injectorCache.clear();
     }
 
-    public Injector get(FunctionModuleData data)
+    /**
+     * Gets an injector initialised with custom functions according to {@link ModuleData#getSoyFunctions()} and
+     * {@link ModuleData#getWrappedFunctions()}. <b>WARNING:</b> Counter-intuitively, the available functions are _not_
+     * effectively tied to the injector, but to the JVM.  So initialising new injectors changes the available functions
+     * to all injectors.  See SOY-5.
+     */
+    public Injector get(ModuleData data)
     {
         return injectorCache.get(data);
     }

soy-template-plugin/src/main/java/com/atlassian/soy/impl/SoyManager.java

 public interface SoyManager
 {
     /**
-     * Make a SoyFileSet builder, optionally loading soy functions from the provided modules. Modules may be
-     * soy-resources modules (loading functions directly) or web-resources modules (loading any functions referenced
-     * by one of the modules dependencies)
+     * Make a SoyFileSet builder, loading soy functions and templates from the provided modules. Modules may be
+     * soy-resource modules (loading functions and templates directly) or web-resource modules (loading any
+     * functions or templates referenced by one of the modules dependencies)
      *
-     * @param functionModuleKeys the plugin keys of any module that can provide soy functions
+     * @param functionModuleKeys the plugin keys of any module that can provide soy functions or templates
      * @return a new Soy builder instance for that module
      */
     public SoyFileSet.Builder makeBuilder(String... functionModuleKeys);
 
     /**
-     * Make a SoyFileSet builder, loading classes from the default modules and any additional modules provided.
-     * @param additionalModules additional modules to load in the Guice {@link com.google.inject.Injector}
-     * @return a new Soy builder instance
-     * @deprecated since 1.1.4, as Modules cannot be used as keys when caching the Guice {@link com.google.inject.Injector}s needed by this method
-     */
-    @Deprecated
-    SoyFileSet.Builder makeBuilder(Module... additionalModules);
-
-    /**
      * Render a template from a module key into an appendable.
      * @param appendable the appendable to write to
-     * @param completeModuleKey - a complete plugin module key
+     * @param completeModuleKey - a complete plugin module key containing the template resource
      * @param templateName - a namespaced Soy template name
      * @param data - a map of data to render the template with
      * @param injectedData - a map of injected data to render the template with

soy-template-plugin/src/test/java/com/atlassian/soy/impl/GlobalFunctionsModuleTest.java

+package com.atlassian.soy.impl;
+
+import com.atlassian.plugin.PluginAccessor;
+import com.atlassian.plugin.hostcontainer.HostContainer;
+import com.atlassian.plugin.module.ModuleFactory;
+import com.atlassian.soy.renderer.JsExpression;
+import com.atlassian.soy.renderer.SoyClientFunction;
+import com.atlassian.soy.renderer.SoyDataMapper;
+import com.atlassian.soy.renderer.SoyFunctionModuleDescriptor;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.template.soy.SoyFileSet;
+import com.google.template.soy.SoyModule;
+import com.google.template.soy.jssrc.SoyJsSrcOptions;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.*;
+import org.mockito.runners.*;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests the GlobalFunctionsModule creates working functions from the pluginAccessor.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class GlobalFunctionsModuleTest
+{
+    private static final String FOO_TEMPLATE_WITH_FUNC_OUTPUT = "// This file was automatically generated from foo.soy.\n" +
+        "// Please don't edit this file by hand.\n" +
+        "\n" +
+        "if (typeof foo == 'undefined') { var foo = {}; }\n" +
+        "\n" +
+        "\n" +
+        "foo.bar = function(opt_data, opt_sb) {\n" +
+        "  var output = opt_sb || new soy.StringBuilder();\n" +
+        "  output.append(soy.$$escapeHtml(MYJS));\n" +
+        "  return opt_sb ? '' : output.toString();\n" +
+        "};\n";
+
+    @Mock private HostContainer hostContainer;
+    @Mock private PluginAccessor pluginAccessor;
+    @Mock private ModuleFactory moduleFactory;
+
+    private GlobalFunctionsModule globalFunctionsModule;
+
+    @Before
+    public void setUp()
+    {
+        SoyDataConverter soyDataConverter = new SoyDataConverter(Collections.<SoyDataMapper>emptyList());
+        globalFunctionsModule = new GlobalFunctionsModule(soyDataConverter, pluginAccessor);
+    }
+
+    @Test
+    public void testGlobalFunctionWorks()
+    {
+        SoyFunctionModuleDescriptor fooFunctionDescriptor = new SoyFunctionModuleDescriptor(moduleFactory, hostContainer);
+        when(hostContainer.create(null)).thenReturn(makeJsFunc("foo"));
+        when(pluginAccessor.getEnabledModuleDescriptorsByClass(SoyFunctionModuleDescriptor.class)).thenReturn(Collections.singletonList(fooFunctionDescriptor));
+
+        Injector injector = Guice.createInjector(globalFunctionsModule, new SoyModule());
+        SoyFileSet.Builder builder = injector.getInstance(SoyFileSet.Builder.class);
+
+        List<String> out = builder.add("{namespace foo}\n/** */\n{template .bar}\n{foo()}\n{/template}\n", "foo.soy").build().compileToJsSrc(new SoyJsSrcOptions(), null);
+        assertEquals(FOO_TEMPLATE_WITH_FUNC_OUTPUT, out.get(0));
+    }
+
+    private SoyClientFunction makeJsFunc(final String functionName)
+    {
+        return new SoyClientFunction()
+        {
+            @Override
+            public String getName()
+            {
+                return functionName;
+            }
+
+            @Override
+            public JsExpression generate(JsExpression... args)
+            {
+                return new JsExpression("MYJS");
+            }
+
+            @Override
+            public Set<Integer> validArgSizes()
+            {
+                return ImmutableSet.of(0);
+            }
+        };
+    }
+
+}

soy-template-plugin/src/test/java/com/atlassian/soy/impl/SoyDependencyInjectorFactoryTest.java

 
     private Injector getDefaultInjector()
     {
-        return injectorFactory.get(new FunctionModuleData(EMPTY_SOY_SERVER_FUNCTIONS, EMPTY_SOY_FUNCTIONS, EMPTY_URLS));
+        return injectorFactory.get(new ModuleData(EMPTY_SOY_SERVER_FUNCTIONS, EMPTY_SOY_FUNCTIONS, EMPTY_URLS));
     }
 
     private Injector getFooInjector()
     {
-        return injectorFactory.get(new FunctionModuleData(EMPTY_SOY_SERVER_FUNCTIONS, fooFunctionSet, EMPTY_URLS));
+        return injectorFactory.get(new ModuleData(EMPTY_SOY_SERVER_FUNCTIONS, fooFunctionSet, EMPTY_URLS));
     }
 
     private Injector getBarInjector()
     {
-        return injectorFactory.get(new FunctionModuleData(EMPTY_SOY_SERVER_FUNCTIONS, barFunctionSet, EMPTY_URLS));
+        return injectorFactory.get(new ModuleData(EMPTY_SOY_SERVER_FUNCTIONS, barFunctionSet, EMPTY_URLS));
     }
 
     @Test
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.