1 package com.atlassian.plugin.webresource;
2
3 import static com.atlassian.plugin.util.EfficientStringUtils.endsWith;
4 import static com.google.common.collect.Iterables.filter;
5
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.TreeMap;
11
12 import com.atlassian.plugin.util.PluginUtils;
13 import org.apache.commons.lang.StringUtils;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16
17 import com.atlassian.plugin.ModuleDescriptor;
18 import com.atlassian.plugin.Plugin;
19 import com.atlassian.plugin.PluginAccessor;
20 import com.atlassian.plugin.Resources;
21 import com.atlassian.plugin.Resources.TypeFilter;
22 import com.atlassian.plugin.elements.ResourceDescriptor;
23 import com.atlassian.plugin.elements.ResourceLocation;
24 import com.atlassian.plugin.servlet.DownloadableClasspathResource;
25 import com.atlassian.plugin.servlet.DownloadableResource;
26 import com.atlassian.plugin.servlet.DownloadableWebResource;
27 import com.atlassian.plugin.servlet.ForwardableResource;
28 import com.atlassian.plugin.servlet.ServletContextFactory;
29 import com.google.common.collect.Iterables;
30
31
32
33
34
35
36 public class PluginResourceLocatorImpl implements PluginResourceLocator
37 {
38 private static final Logger log = LoggerFactory.getLogger(PluginResourceLocatorImpl.class);
39
40 public static final String PLUGIN_WEBRESOURCE_BATCHING_OFF = "plugin.webresource.batching.off";
41
42 private static final String DOWNLOAD_TYPE = "download";
43
44 final private PluginAccessor pluginAccessor;
45 final private ServletContextFactory servletContextFactory;
46 final private ResourceDependencyResolver dependencyResolver;
47
48 private static final String RESOURCE_SOURCE_PARAM = "source";
49 private static final String RESOURCE_BATCH_PARAM = "batch";
50
51 public PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration, final ServletContextFactory servletContextFactory)
52 {
53 this(webResourceIntegration, servletContextFactory, new DefaultResourceDependencyResolver(webResourceIntegration, new DefaultResourceBatchingConfiguration()));
54 }
55
56 public PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration, final ServletContextFactory servletContextFactory,
57 final ResourceBatchingConfiguration resourceBatchingConfiguration)
58 {
59 this(webResourceIntegration, servletContextFactory, new DefaultResourceDependencyResolver(webResourceIntegration, resourceBatchingConfiguration));
60 }
61
62 private PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration, final ServletContextFactory servletContextFactory,
63 final ResourceDependencyResolver dependencyResolver)
64 {
65 this.pluginAccessor = webResourceIntegration.getPluginAccessor();
66 this.servletContextFactory = servletContextFactory;
67 this.dependencyResolver = dependencyResolver;
68 }
69
70 public boolean matches(final String url)
71 {
72 return SuperBatchPluginResource.matches(url) || SuperBatchSubResource.matches(url) || SinglePluginResource.matches(url) || BatchPluginResource.matches(url);
73 }
74
75 public DownloadableResource getDownloadableResource(final String url, final Map<String, String> queryParams)
76 {
77 try
78 {
79 if (SuperBatchPluginResource.matches(url))
80 {
81 return locateSuperBatchPluginResource(SuperBatchPluginResource.parse(url, queryParams));
82 }
83 if (SuperBatchSubResource.matches(url))
84 {
85 return locateSuperBatchSubPluginResource(SuperBatchSubResource.parse(url, queryParams));
86 }
87 if (BatchPluginResource.matches(url))
88 {
89 return locateBatchPluginResource(BatchPluginResource.parse(url, queryParams));
90 }
91 if (SinglePluginResource.matches(url))
92 {
93 final SinglePluginResource resource = SinglePluginResource.parse(url);
94 return locatePluginResource(resource.getModuleCompleteKey(), resource.getResourceName());
95 }
96 }
97 catch (final UrlParseException e)
98 {
99 log.error("Unable to parse URL: " + url, e);
100 }
101
102
103 return null;
104 }
105
106 private DownloadableResource locateSuperBatchPluginResource(final SuperBatchPluginResource batchResource)
107 {
108 if (log.isDebugEnabled())
109 {
110 log.debug(batchResource.toString());
111 }
112
113 for (final String moduleKey : dependencyResolver.getSuperBatchDependencies())
114 {
115 final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(moduleKey);
116 if (moduleDescriptor == null)
117 {
118 log.info("Resource batching configuration refers to plugin that does not exist: " + moduleKey);
119 }
120 else
121 {
122 if (log.isDebugEnabled())
123 {
124 log.debug("searching resources in: " + moduleKey);
125 }
126
127 for (final ResourceDescriptor resourceDescriptor : filter(moduleDescriptor.getResourceDescriptors(), new Resources.TypeFilter(DOWNLOAD_TYPE)))
128 {
129 if (isResourceInBatch(resourceDescriptor, batchResource))
130 {
131 batchResource.add(locatePluginResource(moduleDescriptor.getCompleteKey(), resourceDescriptor.getName()));
132 }
133 }
134 }
135 }
136 return batchResource;
137 }
138
139 private DownloadableResource locateSuperBatchSubPluginResource(final SuperBatchSubResource superBatchSubResource)
140 {
141 for (final String moduleKey : dependencyResolver.getSuperBatchDependencies())
142 {
143 final DownloadableResource pluginResource = locatePluginResource(moduleKey, superBatchSubResource.getResourceName());
144 if (pluginResource != null)
145 {
146 return pluginResource;
147 }
148 }
149 log.warn("Could not locate resource in superbatch: " + superBatchSubResource.getResourceName());
150 return superBatchSubResource;
151 }
152
153 private DownloadableResource locateBatchPluginResource(final BatchPluginResource batchResource)
154 {
155 final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(batchResource.getModuleCompleteKey());
156 for (final ResourceDescriptor resourceDescriptor : Iterables.filter(moduleDescriptor.getResourceDescriptors(), new TypeFilter(DOWNLOAD_TYPE)))
157 {
158 if (isResourceInBatch(resourceDescriptor, batchResource))
159 {
160 batchResource.add(locatePluginResource(moduleDescriptor.getCompleteKey(), resourceDescriptor.getName()));
161 }
162 }
163
164
165 if (batchResource.isEmpty())
166 {
167 final DownloadableResource resource = locatePluginResource(batchResource.getModuleCompleteKey(), batchResource.getResourceName());
168 if (resource != null)
169 {
170 return resource;
171 }
172 }
173
174 return batchResource;
175 }
176
177 private boolean isResourceInBatch(final ResourceDescriptor resourceDescriptor, final BatchResource batchResource)
178 {
179 if (!descriptorTypeMatchesResourceType(resourceDescriptor, batchResource.getType()))
180 {
181 return false;
182 }
183
184 if (skipBatch(resourceDescriptor))
185 {
186 return false;
187 }
188
189 for (final String param : BATCH_PARAMS)
190 {
191 final String batchValue = batchResource.getParams().get(param);
192 final String resourceValue = resourceDescriptor.getParameter(param);
193
194 if ((batchValue == null) && (resourceValue != null))
195 {
196 return false;
197 }
198
199 if ((batchValue != null) && !batchValue.equals(resourceValue))
200 {
201 return false;
202 }
203 }
204
205 return true;
206 }
207
208 private boolean descriptorTypeMatchesResourceType(final ResourceDescriptor resourceDescriptor, final String type)
209 {
210 return endsWith(resourceDescriptor.getName(), ".", type);
211 }
212
213 private DownloadableResource locatePluginResource(final String moduleCompleteKey, final String resourceName)
214 {
215 DownloadableResource resource;
216
217
218 if (moduleCompleteKey.indexOf(":") > -1)
219 {
220 final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(moduleCompleteKey);
221 if (moduleDescriptor != null)
222 {
223 resource = getResourceFromModule(moduleDescriptor, resourceName, "");
224 }
225 else
226 {
227 log.info("Module not found: " + moduleCompleteKey);
228 return null;
229 }
230 }
231 else
232
233 {
234 resource = getResourceFromPlugin(pluginAccessor.getPlugin(moduleCompleteKey), resourceName, "");
235 }
236
237 if (resource == null)
238 {
239 resource = getResourceFromPlugin(getPlugin(moduleCompleteKey), resourceName, "");
240 }
241
242 if (resource == null)
243 {
244 log.info("Unable to find resource for plugin: " + moduleCompleteKey + " and path: " + resourceName);
245 return null;
246 }
247
248 return resource;
249 }
250
251 private Plugin getPlugin(final String moduleKey)
252 {
253 if ((moduleKey.indexOf(':') < 0) || (moduleKey.indexOf(':') == moduleKey.length() - 1))
254 {
255 return null;
256 }
257
258 return pluginAccessor.getPlugin(moduleKey.substring(0, moduleKey.indexOf(':')));
259 }
260
261 private DownloadableResource getResourceFromModule(final ModuleDescriptor<?> moduleDescriptor, final String resourcePath, final String filePath)
262 {
263 final Plugin plugin = pluginAccessor.getPlugin(moduleDescriptor.getPluginKey());
264 final ResourceLocation resourceLocation = moduleDescriptor.getResourceLocation(DOWNLOAD_TYPE, resourcePath);
265
266 if (resourceLocation != null)
267 {
268 boolean disableMinification = false;
269
270
271 if (moduleDescriptor instanceof WebResourceModuleDescriptor)
272 {
273 disableMinification = ((WebResourceModuleDescriptor) moduleDescriptor).isDisableMinification();
274 }
275 return getDownloadablePluginResource(plugin, resourceLocation, moduleDescriptor, filePath, disableMinification);
276 }
277
278 final String[] nextParts = splitLastPathPart(resourcePath);
279 if (nextParts == null)
280 {
281 return null;
282 }
283
284 return getResourceFromModule(moduleDescriptor, nextParts[0], nextParts[1] + filePath);
285 }
286
287 private DownloadableResource getResourceFromPlugin(final Plugin plugin, final String resourcePath, final String filePath)
288 {
289 if (plugin == null)
290 {
291 return null;
292 }
293
294 final ResourceLocation resourceLocation = plugin.getResourceLocation(DOWNLOAD_TYPE, resourcePath);
295 if (resourceLocation != null)
296 {
297 return getDownloadablePluginResource(plugin, resourceLocation, null, filePath, false);
298 }
299
300 final String[] nextParts = splitLastPathPart(resourcePath);
301 if (nextParts == null)
302 {
303 return null;
304 }
305
306 return getResourceFromPlugin(plugin, nextParts[0], nextParts[1] + filePath);
307 }
308
309
310 String[] splitLastPathPart(final String resourcePath)
311 {
312 int indexOfSlash = resourcePath.lastIndexOf('/');
313 if (resourcePath.endsWith("/"))
314 {
315 indexOfSlash = resourcePath.lastIndexOf('/', indexOfSlash - 1);
316 }
317
318 if (indexOfSlash < 0)
319 {
320 return null;
321 }
322
323 return new String[] { resourcePath.substring(0, indexOfSlash + 1), resourcePath.substring(indexOfSlash + 1) };
324 }
325
326 private DownloadableResource getDownloadablePluginResource(final Plugin plugin, final ResourceLocation resourceLocation,
327 ModuleDescriptor descriptor, final String filePath,
328 final boolean disableMinification)
329 {
330 final String sourceParam = resourceLocation.getParameter(RESOURCE_SOURCE_PARAM);
331
332
333
334 if ("webContext".equalsIgnoreCase(sourceParam))
335 {
336 return new ForwardableResource(resourceLocation);
337 }
338
339 DownloadableResource actualResource = null;
340
341 if ("webContextStatic".equalsIgnoreCase(sourceParam))
342 {
343 actualResource = new DownloadableWebResource(plugin, resourceLocation, filePath, servletContextFactory.getServletContext(), disableMinification);
344 }
345 else
346 {
347 actualResource = new DownloadableClasspathResource(plugin, resourceLocation, filePath);
348 }
349
350 DownloadableResource result = actualResource;
351
352 if (descriptor instanceof WebResourceModuleDescriptor)
353 {
354 DownloadableResource lastResource = actualResource;
355 WebResourceModuleDescriptor desc = (WebResourceModuleDescriptor) descriptor;
356 for (WebResourceTransformation list : desc.getTransformations())
357 {
358 if (list.matches(resourceLocation))
359 {
360 lastResource = list.transformDownloadableResource(pluginAccessor, actualResource, resourceLocation, filePath);
361 }
362 }
363 result = lastResource;
364 }
365 return result;
366 }
367
368 public List<PluginResource> getPluginResources(final String moduleCompleteKey)
369 {
370 final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(moduleCompleteKey);
371 if ((moduleDescriptor == null) || !(moduleDescriptor instanceof WebResourceModuleDescriptor))
372 {
373 log.error("Error loading resource \"" + moduleCompleteKey + "\". Resource is not a Web Resource Module");
374 return Collections.emptyList();
375 }
376
377 final boolean singleMode = isBatchingOff();
378 final List<PluginResource> resources = new ArrayList<PluginResource>();
379
380 for (final ResourceDescriptor resourceDescriptor : moduleDescriptor.getResourceDescriptors())
381 {
382 if (singleMode || skipBatch(resourceDescriptor))
383 {
384 final boolean cache = !"false".equalsIgnoreCase(resourceDescriptor.getParameter("cache"));
385 resources.add(new SinglePluginResource(resourceDescriptor.getName(), moduleDescriptor.getCompleteKey(), cache, resourceDescriptor.getParameters()));
386 }
387 else
388 {
389 final BatchPluginResource batchResource = createBatchResource(moduleDescriptor.getCompleteKey(), resourceDescriptor);
390 if (!resources.contains(batchResource))
391 {
392 resources.add(batchResource);
393 }
394 }
395 }
396 return resources;
397 }
398
399
400
401
402 Boolean isBatchingOff()
403 {
404 String explicitSetting = System.getProperty(PLUGIN_WEBRESOURCE_BATCHING_OFF);
405 if (explicitSetting != null)
406 {
407 return Boolean.parseBoolean(explicitSetting);
408 }
409 else
410 {
411 return Boolean.parseBoolean(System.getProperty(PluginUtils.ATLASSIAN_DEV_MODE));
412 }
413
414 }
415
416 private boolean skipBatch(final ResourceDescriptor resourceDescriptor)
417 {
418
419 return "false".equalsIgnoreCase(resourceDescriptor.getParameter(RESOURCE_BATCH_PARAM))
420 || "webContext".equalsIgnoreCase(resourceDescriptor.getParameter(RESOURCE_SOURCE_PARAM));
421 }
422
423 private BatchPluginResource createBatchResource(final String moduleCompleteKey, final ResourceDescriptor resourceDescriptor)
424 {
425 final String name = resourceDescriptor.getName();
426 final String type = name.substring(name.lastIndexOf(".") + 1);
427 final Map<String, String> params = new TreeMap<String, String>();
428 for (final String param : BATCH_PARAMS)
429 {
430 final String value = resourceDescriptor.getParameter(param);
431 if (StringUtils.isNotEmpty(value))
432 {
433 params.put(param, value);
434 }
435 }
436
437 return new BatchPluginResource(moduleCompleteKey, type, params);
438 }
439
440 public String getResourceUrl(final String moduleCompleteKey, final String resourceName)
441 {
442 return new SinglePluginResource(resourceName, moduleCompleteKey, false).getUrl();
443 }
444 }