View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import org.apache.commons.collections.set.ListOrderedSet;
6   import org.apache.commons.logging.Log;
7   import org.apache.commons.logging.LogFactory;
8   
9   import java.io.IOException;
10  import java.io.StringWriter;
11  import java.io.Writer;
12  import java.util.*;
13  
14  /**
15   * A handy super-class that handles most of the resource management.
16   * <p/>
17   * To use this manager, you need to have the following UrlRewriteFilter code:
18   * <pre>
19   * &lt;rule>
20   * &lt;from>^/s/(.*)/_/(.*)&lt;/from>
21   * &lt;run class="com.atlassian.plugin.servlet.ResourceDownloadUtils" method="addCachingHeaders" />
22   * &lt;to type="forward">/$2&lt;/to>
23   * &lt;/rule>
24   * </pre>
25   * <p/>
26   * Sub-classes should implement the abstract methods
27   */
28  public class WebResourceManagerImpl implements WebResourceManager
29  {
30      private static final Log log = LogFactory.getLog(WebResourceManagerImpl.class);
31  
32      static final String STATIC_RESOURCE_PREFIX = "s";
33      static final String STATIC_RESOURCE_SUFFIX = "_";
34  
35      static final String REQUEST_CACHE_RESOURCE_KEY = "plugin.webresource.names";
36  
37      protected final WebResourceIntegration webResourceIntegration;
38      protected final PluginResourceLocator pluginResourceLocator;
39      protected static final List<WebResourceFormatter> webResourceFormatters = Arrays.< WebResourceFormatter>asList(
40          new CssWebResourceFormatter(),
41          new JavascriptWebResourceFormatter()
42      );
43  
44      public WebResourceManagerImpl(PluginResourceLocator pluginResourceLocator, WebResourceIntegration webResourceIntegration)
45      {
46          this.pluginResourceLocator = pluginResourceLocator;
47          this.webResourceIntegration = webResourceIntegration;
48      }
49  
50      public void requireResource(String moduleCompleteKey)
51      {
52          log.info("Requiring resource: " + moduleCompleteKey);
53          Map cache = webResourceIntegration.getRequestCache();
54          Collection webResourceNames = (Collection) cache.get(REQUEST_CACHE_RESOURCE_KEY);
55          if (webResourceNames == null)
56          {
57              webResourceNames = new ListOrderedSet();
58          }
59  
60          ListOrderedSet resources = new ListOrderedSet();
61          addResourceWithDependencies(moduleCompleteKey, resources, new Stack<String>());
62          webResourceNames.addAll(resources);
63          cache.put(REQUEST_CACHE_RESOURCE_KEY, webResourceNames);
64      }
65  
66      /**
67       * Adds the resources as well as its dependencies in order to the given set. This method uses recursion
68       * to add a resouce's dependent resources also to the set. You should call this method with a new stack
69       * passed to the last parameter.
70       *
71       * @param moduleKey the module complete key to add as well as its dependencies
72       * @param orderedResourceKeys an ordered list set where the resources are added in order
73       * @param stack where we are in the dependency tree
74       */
75      private void addResourceWithDependencies(String moduleKey, ListOrderedSet orderedResourceKeys, Stack<String> stack)
76      {
77          if (stack.contains(moduleKey))
78          {
79              log.warn("Cyclic plugin resource dependency has been detected with: " + moduleKey + "\n" +
80                  "Stack trace: " + stack);
81              return;
82          }
83  
84          ModuleDescriptor moduleDescriptor = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(moduleKey);
85          if (!(moduleDescriptor instanceof WebResourceModuleDescriptor))
86          {
87              log.warn("Cannot find web resource module for: " + moduleKey);
88              return;
89          }
90  
91          List<String> dependencies = ((WebResourceModuleDescriptor) moduleDescriptor).getDependencies();
92          log.info("About to add resource [" + moduleKey + "] and its dependencies: " + dependencies);
93  
94          stack.push(moduleKey);
95          try
96          {
97              for (String dependency : dependencies)
98              {
99                  if (!orderedResourceKeys.contains(dependency))
100                     addResourceWithDependencies(dependency, orderedResourceKeys, stack);
101             }
102         }
103         finally
104         {
105             stack.pop();
106         }
107         orderedResourceKeys.add(moduleKey);
108     }
109 
110     public void includeResources(Writer writer)
111     {
112         Collection webResourceNames = (Collection) webResourceIntegration.getRequestCache().get(REQUEST_CACHE_RESOURCE_KEY);
113         if (webResourceNames == null || webResourceNames.isEmpty())
114         {
115             log.info("No resources required to write");
116             return;
117         }
118 
119         for (Object webResourceName : webResourceNames)
120         {
121             String resourceName = (String) webResourceName;
122             requireResource(resourceName, writer);
123         }
124     }
125 
126     public String getRequiredResources()
127     {
128         StringWriter writer = new StringWriter();
129         includeResources(writer);
130         return writer.toString();
131     }
132 
133     public void requireResource(String moduleCompleteKey, Writer writer)
134     {
135         List<PluginResource> resources = pluginResourceLocator.getPluginResources(moduleCompleteKey);
136         if (resources == null)
137         {
138             writeContentAndSwallowErrors("<!-- Error loading resource \"" + moduleCompleteKey + "\".  Resource not found -->\n", writer);
139             return;
140         }
141 
142         for (PluginResource resource : resources)
143         {
144             WebResourceFormatter formatter = getWebResourceFormatter(resource.getResourceName());
145             if (formatter == null)
146             {
147                 writeContentAndSwallowErrors("<!-- Error loading resource \"" + moduleCompleteKey + "\".  Resource formatter not found -->\n", writer);
148                 continue;
149             }
150 
151             String url = resource.getUrl();
152             if (resource.isCacheSupported())
153             {
154                 Plugin plugin = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(resource.getModuleCompleteKey()).getPlugin();
155                 url = getStaticResourcePrefix(plugin.getPluginInformation().getVersion()) + url;
156             }
157             writeContentAndSwallowErrors(formatter.formatResource(url, resource.getParams()), writer);
158         }
159     }
160 
161     public String getResourceTags(String moduleCompleteKey)
162     {
163         StringWriter writer = new StringWriter();
164         requireResource(moduleCompleteKey, writer);
165         return writer.toString();
166     }
167 
168     private void writeContentAndSwallowErrors(String content, Writer writer)
169     {
170         try
171         {
172             writer.write(content);
173         }
174         catch (IOException e)
175         {
176             log.error(e);
177         }
178     }
179 
180     private WebResourceFormatter getWebResourceFormatter(String resourceName)
181     {
182         for (WebResourceFormatter webResourceFormatter : webResourceFormatters)
183         {
184             if (webResourceFormatter.matches(resourceName))
185                 return webResourceFormatter;
186         }
187         return null;
188     }
189 
190     public String getStaticResourcePrefix()
191     {
192         // "{base url}/s/{build num}/{system counter}/_"
193         return webResourceIntegration.getBaseUrl() + "/" +
194                 STATIC_RESOURCE_PREFIX + "/" +
195                 webResourceIntegration.getSystemBuildNumber() + "/" +
196                 webResourceIntegration.getSystemCounter() + "/" +
197                 STATIC_RESOURCE_SUFFIX;
198     }
199 
200     public String getStaticResourcePrefix(String resourceCounter)
201     {
202         // "{base url}/s/{build num}/{system counter}/{resource counter}/_"
203         return webResourceIntegration.getBaseUrl() + "/" +
204                 STATIC_RESOURCE_PREFIX + "/" +
205                 webResourceIntegration.getSystemBuildNumber() + "/" +
206                 webResourceIntegration.getSystemCounter() + "/" +
207                 resourceCounter + "/" +
208                 STATIC_RESOURCE_SUFFIX;
209     }
210 
211     public String getStaticPluginResource(String moduleCompleteKey, String resourceName)
212     {
213         ModuleDescriptor moduleDescriptor = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(moduleCompleteKey);
214         if(moduleDescriptor == null)
215             return null;
216 
217         return getStaticPluginResource(moduleDescriptor, resourceName);
218     }
219 
220     /**
221      * @return "{base url}/s/{build num}/{system counter}/{plugin version}/_/download/resources/{plugin.key:module.key}/{resource.name}"
222      */
223     public String getStaticPluginResource(ModuleDescriptor moduleDescriptor, String resourceName)
224     {
225         // "{base url}/s/{build num}/{system counter}/{plugin version}/_"
226         String staticUrlPrefix = getStaticResourcePrefix(String.valueOf(moduleDescriptor.getPlugin().getPluginsVersion()));
227         // "/download/resources/plugin.key:module.key/resource.name"
228         return staticUrlPrefix + pluginResourceLocator.getResourceUrl(moduleDescriptor.getCompleteKey(), resourceName);
229     }
230 
231     /* Deprecated methods */
232 
233     /**
234      * @deprecated Use {@link #getStaticPluginResource(com.atlassian.plugin.ModuleDescriptor, String)} instead
235      */
236     public String getStaticPluginResourcePrefix(ModuleDescriptor moduleDescriptor, String resourceName)
237     {
238         return getStaticPluginResource(moduleDescriptor, resourceName);
239     }
240 
241     /**
242      * @deprecated Since 2.2
243      */
244     private static final String REQUEST_CACHE_MODE_KEY = "plugin.webresource.mode";
245 
246     /**
247      * @deprecated Since 2.2
248      */
249     private static final IncludeMode DEFAULT_INCLUDE_MODE = WebResourceManager.DELAYED_INCLUDE_MODE;
250 
251     /**
252      * @deprecated Since 2.2.
253      */
254     public void setIncludeMode(IncludeMode includeMode)
255     {
256         webResourceIntegration.getRequestCache().put(REQUEST_CACHE_MODE_KEY, includeMode);
257     }
258 
259     IncludeMode getIncludeMode()
260     {
261         IncludeMode includeMode = (IncludeMode) webResourceIntegration.getRequestCache().get(REQUEST_CACHE_MODE_KEY);
262         if (includeMode == null)
263         {
264             includeMode = DEFAULT_INCLUDE_MODE;
265         }
266         return includeMode;
267     }
268 }