View Javadoc

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