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.debug("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.debug("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.debug("No resources required to write");
116             return;
117         }
118 
119         for (Object webResourceName : webResourceNames)
120         {
121             String resourceName = (String) webResourceName;
122             writeResourceTag(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         ListOrderedSet resourcesWithDeps = new ListOrderedSet();
136         addResourceWithDependencies(moduleCompleteKey, resourcesWithDeps, new Stack<String>());
137 
138         for (Object resource : resourcesWithDeps)
139         {
140             writeResourceTag((String) resource, writer);
141         }
142     }
143 
144     private void writeResourceTag(String moduleCompleteKey, Writer writer)
145     {
146         List<PluginResource> resources = pluginResourceLocator.getPluginResources(moduleCompleteKey);
147 
148         if (resources == null)
149         {
150             writeContentAndSwallowErrors("<!-- Error loading resource \"" + moduleCompleteKey + "\".  Resource not found -->\n", writer);
151             return;
152         }
153 
154         for (PluginResource resource : resources)
155         {
156             WebResourceFormatter formatter = getWebResourceFormatter(resource.getResourceName());
157             if (formatter == null)
158             {
159                 writeContentAndSwallowErrors("<!-- Error loading resource \"" + moduleCompleteKey + "\".  Resource formatter not found -->\n", writer);
160                 continue;
161             }
162 
163             String url = resource.getUrl();
164             if (resource.isCacheSupported())
165             {
166                 Plugin plugin = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(resource.getModuleCompleteKey()).getPlugin();
167                 url = getStaticResourcePrefix(plugin.getPluginInformation().getVersion()) + url;
168             }
169             else
170             {
171                 url = webResourceIntegration.getBaseUrl() + url;
172             }
173             writeContentAndSwallowErrors(formatter.formatResource(url, resource.getParams()), writer);
174         }
175     }
176 
177     public String getResourceTags(String moduleCompleteKey)
178     {
179         StringWriter writer = new StringWriter();
180         requireResource(moduleCompleteKey, writer);
181         return writer.toString();
182     }
183 
184     private void writeContentAndSwallowErrors(String content, Writer writer)
185     {
186         try
187         {
188             writer.write(content);
189         }
190         catch (IOException e)
191         {
192             log.error(e);
193         }
194     }
195 
196     private WebResourceFormatter getWebResourceFormatter(String resourceName)
197     {
198         for (WebResourceFormatter webResourceFormatter : webResourceFormatters)
199         {
200             if (webResourceFormatter.matches(resourceName))
201                 return webResourceFormatter;
202         }
203         return null;
204     }
205 
206     public String getStaticResourcePrefix()
207     {
208         // "{base url}/s/{build num}/{system counter}/_"
209         return webResourceIntegration.getBaseUrl() + "/" +
210                 STATIC_RESOURCE_PREFIX + "/" +
211                 webResourceIntegration.getSystemBuildNumber() + "/" +
212                 webResourceIntegration.getSystemCounter() + "/" +
213                 STATIC_RESOURCE_SUFFIX;
214     }
215 
216     public String getStaticResourcePrefix(String resourceCounter)
217     {
218         // "{base url}/s/{build num}/{system counter}/{resource counter}/_"
219         return webResourceIntegration.getBaseUrl() + "/" +
220                 STATIC_RESOURCE_PREFIX + "/" +
221                 webResourceIntegration.getSystemBuildNumber() + "/" +
222                 webResourceIntegration.getSystemCounter() + "/" +
223                 resourceCounter + "/" +
224                 STATIC_RESOURCE_SUFFIX;
225     }
226 
227     public String getStaticPluginResource(String moduleCompleteKey, String resourceName)
228     {
229         ModuleDescriptor moduleDescriptor = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(moduleCompleteKey);
230         if(moduleDescriptor == null)
231             return null;
232 
233         return getStaticPluginResource(moduleDescriptor, resourceName);
234     }
235 
236     /**
237      * @return "{base url}/s/{build num}/{system counter}/{plugin version}/_/download/resources/{plugin.key:module.key}/{resource.name}"
238      */
239     public String getStaticPluginResource(ModuleDescriptor moduleDescriptor, String resourceName)
240     {
241         // "{base url}/s/{build num}/{system counter}/{plugin version}/_"
242         String staticUrlPrefix = getStaticResourcePrefix(String.valueOf(moduleDescriptor.getPlugin().getPluginsVersion()));
243         // "/download/resources/plugin.key:module.key/resource.name"
244         return staticUrlPrefix + pluginResourceLocator.getResourceUrl(moduleDescriptor.getCompleteKey(), resourceName);
245     }
246 
247     /* Deprecated methods */
248 
249     /**
250      * @deprecated Use {@link #getStaticPluginResource(com.atlassian.plugin.ModuleDescriptor, String)} instead
251      */
252     public String getStaticPluginResourcePrefix(ModuleDescriptor moduleDescriptor, String resourceName)
253     {
254         return getStaticPluginResource(moduleDescriptor, resourceName);
255     }
256 
257     /**
258      * @deprecated Since 2.2
259      */
260     private static final String REQUEST_CACHE_MODE_KEY = "plugin.webresource.mode";
261 
262     /**
263      * @deprecated Since 2.2
264      */
265     private static final IncludeMode DEFAULT_INCLUDE_MODE = WebResourceManager.DELAYED_INCLUDE_MODE;
266 
267     /**
268      * @deprecated Since 2.2.
269      */
270     public void setIncludeMode(IncludeMode includeMode)
271     {
272         webResourceIntegration.getRequestCache().put(REQUEST_CACHE_MODE_KEY, includeMode);
273     }
274 
275     IncludeMode getIncludeMode()
276     {
277         IncludeMode includeMode = (IncludeMode) webResourceIntegration.getRequestCache().get(REQUEST_CACHE_MODE_KEY);
278         if (includeMode == null)
279         {
280             includeMode = DEFAULT_INCLUDE_MODE;
281         }
282         return includeMode;
283     }
284 }