1 package com.atlassian.plugin.webresource;
2
3 import static com.atlassian.plugin.util.Assertions.notNull;
4 import static com.google.common.collect.ImmutableMap.copyOf;
5 import static com.google.common.collect.Iterables.addAll;
6 import static com.google.common.collect.Iterables.concat;
7 import static com.google.common.collect.Iterables.contains;
8 import static com.google.common.collect.Iterables.transform;
9
10 import com.atlassian.plugin.ModuleDescriptor;
11
12 import com.google.common.base.Supplier;
13 import com.google.common.collect.ImmutableSet;
14 import com.google.common.collect.Lists;
15 import com.google.common.collect.Sets;
16
17 import java.io.IOException;
18 import java.io.StringWriter;
19 import java.io.Writer;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31 import org.apache.commons.collections.CollectionUtils;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public class WebResourceManagerImpl implements WebResourceManager
48 {
49 static final String STATIC_RESOURCE_PREFIX = "s";
50 static final String STATIC_RESOURCE_SUFFIX = "_";
51
52 static final String REQUEST_CACHE_RESOURCE_KEY = "plugin.webresource.names";
53 static final String REQUEST_CACHE_CONTEXT_KEY = "plugin.webresource.contexts";
54
55 protected final WebResourceIntegration webResourceIntegration;
56 protected final PluginResourceLocator pluginResourceLocator;
57 private final WebResourceUrlProvider webResourceUrlProvider;
58 protected final ResourceBatchingConfiguration batchingConfiguration;
59 protected final ResourceDependencyResolver dependencyResolver;
60 protected static final List<WebResourceFormatter> webResourceFormatters = Arrays.asList(CssWebResource.FORMATTER, JavascriptWebResource.FORMATTER);
61
62 private static final boolean IGNORE_SUPERBATCHING = false;
63
64 public WebResourceManagerImpl(final PluginResourceLocator pluginResourceLocator, final WebResourceIntegration webResourceIntegration, final WebResourceUrlProvider webResourceUrlProvider)
65 {
66 this(pluginResourceLocator, webResourceIntegration, webResourceUrlProvider, new DefaultResourceBatchingConfiguration());
67 }
68
69 public WebResourceManagerImpl(final PluginResourceLocator pluginResourceLocator, final WebResourceIntegration webResourceIntegration, final WebResourceUrlProvider webResourceUrlProvider, final ResourceBatchingConfiguration batchingConfiguration)
70 {
71 this(pluginResourceLocator, webResourceIntegration, webResourceUrlProvider, batchingConfiguration, new DefaultResourceDependencyResolver(
72 webResourceIntegration, batchingConfiguration));
73 }
74
75 public WebResourceManagerImpl(final PluginResourceLocator pluginResourceLocator, final WebResourceIntegration webResourceIntegration, final WebResourceUrlProvider webResourceUrlProvider, final ResourceBatchingConfiguration batchingConfiguration, final ResourceDependencyResolver dependencyResolver)
76 {
77 this.pluginResourceLocator = notNull("pluginResourceLocator", pluginResourceLocator);
78 this.webResourceIntegration = notNull("webResourceIntegration", webResourceIntegration);
79 this.webResourceUrlProvider = notNull("webResourceUrlProvider", webResourceUrlProvider);
80 this.batchingConfiguration = notNull("batchingConfiguration", batchingConfiguration);
81 this.dependencyResolver = notNull("dependencyResolver", dependencyResolver);
82 }
83
84 public void requireResource(final String moduleCompleteKey)
85 {
86 final boolean batchingEnabled = batchingConfiguration.isSuperBatchingEnabled();
87 addAll(getIncludedResourceNames(), toModuleKeys(dependencyResolver.getDependencies(moduleCompleteKey, batchingEnabled)));
88 }
89
90 public void requireResourcesForContext(final String context)
91 {
92 getIncludedContexts(false).add(context);
93 }
94
95 public void includeResources(final Iterable<String> moduleCompleteKeys, final Writer writer, final UrlMode urlMode)
96 {
97 Iterable<String> resources = Lists.newArrayList();
98 for (final String moduleCompleteKey : moduleCompleteKeys)
99 {
100
101 final Iterable<String> dependencies = toModuleKeys(dependencyResolver.getDependencies(moduleCompleteKey, false));
102 resources = concat(resources, dependencies);
103 }
104
105
106 resources = ImmutableSet.copyOf(resources);
107 writeResourceTags(getModuleResources(resources, Collections.<String> emptyList(), DefaultWebResourceFilter.INSTANCE), writer, urlMode);
108 }
109
110
111
112
113
114
115
116 public void includeResources(final Writer writer, final UrlMode urlMode)
117 {
118 includeResources(writer, urlMode, DefaultWebResourceFilter.INSTANCE);
119 }
120
121
122
123
124
125
126
127
128
129
130 public void includeResources(final Writer writer, final UrlMode urlMode, final WebResourceFilter webResourceFilter)
131 {
132 writeIncludedResources(writer, urlMode, webResourceFilter);
133 clear();
134 }
135
136
137
138
139
140
141
142 public String getRequiredResources(final UrlMode urlMode)
143 {
144 return getRequiredResources(urlMode, DefaultWebResourceFilter.INSTANCE);
145 }
146
147
148
149
150
151
152
153
154
155
156
157 public String getRequiredResources(final UrlMode urlMode, final WebResourceFilter filter)
158 {
159 return writeIncludedResources(new StringWriter(), urlMode, filter).toString();
160 }
161
162
163
164
165 private <W extends Writer> W writeIncludedResources(final W writer, final UrlMode urlMode, final WebResourceFilter filter)
166 {
167 final ContextBatchBuilder builder = new ContextBatchBuilder(pluginResourceLocator, dependencyResolver, batchingConfiguration);
168 final Iterable<PluginResource> resourcesToInclude = concat(getSuperBatchResources(filter), builder.build(getIncludedContexts(false), filter));
169 for (final String skippedResource : builder.getSkippedResources())
170 {
171 requireResource(skippedResource);
172 }
173 final Iterable<PluginResource> moduleResources = getModuleResources(getIncludedResourceNames(), builder.getAllIncludedResources(), filter);
174 return writeResourceTags(concat(resourcesToInclude, moduleResources), writer, urlMode);
175 }
176
177
178
179
180
181
182
183 List<PluginResource> getSuperBatchResources(final WebResourceFilter filter)
184 {
185 if (!batchingConfiguration.isSuperBatchingEnabled())
186 {
187 return Collections.emptyList();
188 }
189
190 final Iterable<WebResourceModuleDescriptor> superBatchModuleKeys = dependencyResolver.getSuperBatchDependencies();
191 final List<PluginResource> resources = new ArrayList<PluginResource>();
192
193
194
195
196
197 for (final WebResourceFormatter formatter : webResourceFormatters)
198 {
199 final Set<Map<String, String>> alreadyIncluded = new HashSet<Map<String, String>>();
200 for (final WebResourceModuleDescriptor moduleDescriptor : superBatchModuleKeys)
201 {
202 for (final PluginResource pluginResource : pluginResourceLocator.getPluginResources(moduleDescriptor.getCompleteKey()))
203 {
204 if (formatter.matches(pluginResource.getResourceName()) && filter.matches(pluginResource.getResourceName()))
205 {
206 final Map<String, String> batchParamsMap = new HashMap<String, String>(PluginResourceLocator.BATCH_PARAMS.length);
207 for (final String s : PluginResourceLocator.BATCH_PARAMS)
208 {
209 batchParamsMap.put(s, pluginResource.getParams().get(s));
210 }
211
212 if (!alreadyIncluded.contains(batchParamsMap))
213 {
214 resources.add(SuperBatchPluginResource.createBatchFor(pluginResource));
215 alreadyIncluded.add(batchParamsMap);
216 }
217 }
218 }
219 }
220 }
221 return resources;
222 }
223
224
225
226
227
228 private <W extends Writer> W writeResourceTags(final Iterable<PluginResource> resourcesToInclude, final W writer, final UrlMode urlMode)
229 {
230 for (final WebResourceFormatter formatter : webResourceFormatters)
231 {
232 for (final Iterator<PluginResource> iter = resourcesToInclude.iterator(); iter.hasNext();)
233 {
234 final PluginResource resource = iter.next();
235 if (formatter.matches(resource.getResourceName()))
236 {
237 writeResourceTag(urlMode, resource, formatter, writer);
238 iter.remove();
239 }
240 }
241 }
242
243 for (final PluginResource resource : resourcesToInclude)
244 {
245 writeContentAndSwallowErrors(writer, "<!-- Error loading resource \"", resource.getModuleCompleteKey(),
246 "\". No resource formatter matches \"", resource.getResourceName(), "\" -->\n");
247 }
248 return writer;
249 }
250
251 private void writeResourceTag(final UrlMode urlMode, final PluginResource resource, final WebResourceFormatter formatter, final Writer writer)
252 {
253 final String prefix;
254 if (resource.isCacheSupported())
255 {
256 prefix = webResourceUrlProvider.getStaticResourcePrefix(resource.getVersion(webResourceIntegration), urlMode);
257 }
258 else
259 {
260 prefix = webResourceUrlProvider.getBaseUrl(urlMode);
261 }
262 writeContentAndSwallowErrors(writer, formatter.formatResource(prefix + resource.getUrl(), resource.getParams()));
263 }
264
265 public void requireResource(final String moduleCompleteKey, final Writer writer, final UrlMode urlMode)
266 {
267 final Iterable<String> allDependentModuleKeys = toModuleKeys(dependencyResolver.getDependencies(moduleCompleteKey, IGNORE_SUPERBATCHING));
268 final Iterable<String> empty = Collections.<String> emptyList();
269 final Iterable<PluginResource> resourcesToInclude = getModuleResources(allDependentModuleKeys, empty, DefaultWebResourceFilter.INSTANCE);
270 writeResourceTags(resourcesToInclude, writer, urlMode);
271 }
272
273 public String getResourceTags(final String moduleCompleteKey, final UrlMode urlMode)
274 {
275 final StringWriter writer = new StringWriter();
276 requireResource(moduleCompleteKey, writer, urlMode);
277 return writer.toString();
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297 protected String getResourceTagsForContexts(final Set<String> contexts, final Set<String> existingContexts, final WebResourceFilter filter, final UrlMode urlMode)
298 {
299 final ContextBatchBuilder builder = new ContextBatchBuilder(pluginResourceLocator, dependencyResolver, batchingConfiguration);
300 Iterable<PluginResource> pluginResources = builder.build(contexts, existingContexts, filter);
301
302
303 if (CollectionUtils.isEmpty(existingContexts))
304 {
305 pluginResources = concat(getSuperBatchResources(filter), pluginResources);
306 }
307
308 Set<String> includedResources = new HashSet<String>();
309
310 for (final String skippedResource : builder.getSkippedResources())
311 {
312 Iterable<String> moduleKeys = toModuleKeys(dependencyResolver.getDependencies(skippedResource, batchingConfiguration.isSuperBatchingEnabled()));
313 addAll(includedResources, moduleKeys);
314 }
315
316 final Iterable<PluginResource> moduleResources = getModuleResources(includedResources, builder.getAllIncludedResources(), filter);
317 return writeResourceTags(concat(pluginResources, moduleResources), new StringWriter(), urlMode).toString();
318 }
319
320 public String getStaticResourcePrefix(final String resourceCounter, final UrlMode urlMode)
321 {
322 return webResourceUrlProvider.getStaticResourcePrefix(resourceCounter, urlMode);
323 }
324
325 public String getStaticPluginResource(final String moduleCompleteKey, final String resourceName, final UrlMode urlMode)
326 {
327 return webResourceUrlProvider.getStaticPluginResourceUrl(moduleCompleteKey, resourceName, urlMode);
328 }
329
330
331
332
333 public String getStaticPluginResource(final ModuleDescriptor<?> moduleDescriptor, final String resourceName, final UrlMode urlMode)
334 {
335 return webResourceUrlProvider.getStaticPluginResourceUrl(moduleDescriptor, resourceName, urlMode);
336 }
337
338 public <T> T executeInNewContext(final Supplier<T> nestedExecution)
339 {
340 final Map<String, Object> cache = webResourceIntegration.getRequestCache();
341 final Map<String, Object> storedState = copyOf(cache);
342
343
344 cache.clear();
345 try
346 {
347 return nestedExecution.get();
348 }
349 finally
350 {
351
352
353
354 cache.clear();
355 cache.putAll(storedState);
356 }
357 }
358
359
360
361
362
363 private Iterable<PluginResource> getModuleResources(final Iterable<String> webResourcePluginModuleKeys, final Iterable<String> batchedModules, final WebResourceFilter filter)
364 {
365 final List<PluginResource> includedResources = new LinkedList<PluginResource>();
366 for (final String moduleKey : webResourcePluginModuleKeys)
367 {
368 if (contains(batchedModules, moduleKey))
369 {
370
371 continue;
372 }
373
374 final List<PluginResource> moduleResources = pluginResourceLocator.getPluginResources(moduleKey);
375 for (final PluginResource moduleResource : moduleResources)
376 {
377 if (filter.matches(moduleResource.getResourceName()))
378 {
379 includedResources.add(moduleResource);
380 }
381 }
382 }
383 return includedResources;
384 }
385
386 private void writeContentAndSwallowErrors(final Writer writer, final String... contents)
387 {
388 try
389 {
390 for (final String content : contents)
391 {
392 writer.write(content);
393 }
394 }
395 catch (final IOException ignore)
396 {}
397 }
398
399 public Set<String> getIncludedContexts()
400 {
401 return getIncludedContexts(true);
402 }
403
404
405
406
407
408
409
410 private Set<String> getIncludedContexts(boolean copy)
411 {
412 Set<String> cachedSet = getOrCreateFromRequestCache(REQUEST_CACHE_CONTEXT_KEY);
413 if (copy)
414 {
415 return new HashSet<String>(cachedSet);
416 }
417 else
418 {
419 return cachedSet;
420 }
421 }
422
423 private Set<String> getIncludedResourceNames()
424 {
425 return getOrCreateFromRequestCache(REQUEST_CACHE_RESOURCE_KEY);
426 }
427
428 private Set<String> getOrCreateFromRequestCache(final String key)
429 {
430 final Map<String, Object> cache = webResourceIntegration.getRequestCache();
431 @SuppressWarnings("unchecked")
432 Set<String> set = (Set<String>) cache.get(key);
433 if (set == null)
434 {
435 set = Sets.newLinkedHashSet();
436 cache.put(key, set);
437 }
438 return set;
439 }
440
441 private void clear()
442 {
443 getIncludedResourceNames().clear();
444 getIncludedContexts(false).clear();
445 }
446
447 private Iterable<String> toModuleKeys(final Iterable<WebResourceModuleDescriptor> descriptors)
448 {
449 return transform(descriptors, new TransformDescriptorToKey());
450 }
451
452
453
454
455
456 @Deprecated
457 public String getRequiredResources()
458 {
459 return getRequiredResources(UrlMode.AUTO);
460 }
461
462 @Deprecated
463 public String getResourceTags(final String moduleCompleteKey)
464 {
465 return getResourceTags(moduleCompleteKey, UrlMode.AUTO);
466 }
467
468 @Deprecated
469 public String getStaticPluginResource(final ModuleDescriptor<?> moduleDescriptor, final String resourceName)
470 {
471 return getStaticPluginResource(moduleDescriptor, resourceName, UrlMode.AUTO);
472 }
473
474 @Deprecated
475 public String getStaticPluginResource(final String moduleCompleteKey, final String resourceName)
476 {
477 return getStaticPluginResource(moduleCompleteKey, resourceName, UrlMode.AUTO);
478 }
479
480 @Deprecated
481 public String getStaticPluginResourcePrefix(final ModuleDescriptor<?> moduleDescriptor, final String resourceName)
482 {
483 return getStaticPluginResource(moduleDescriptor, resourceName, UrlMode.AUTO);
484 }
485
486 @Deprecated
487 public String getStaticResourcePrefix()
488 {
489 return webResourceUrlProvider.getStaticResourcePrefix(UrlMode.AUTO);
490 }
491
492 @Deprecated
493 public String getStaticResourcePrefix(final UrlMode urlMode)
494 {
495 return webResourceUrlProvider.getStaticResourcePrefix(urlMode);
496 }
497
498 @Deprecated
499 public String getStaticResourcePrefix(final String resourceCounter)
500 {
501 return getStaticResourcePrefix(resourceCounter, UrlMode.AUTO);
502 }
503
504 @Deprecated
505 public void includeResources(final Writer writer)
506 {
507 includeResources(writer, UrlMode.AUTO);
508 }
509
510 @Deprecated
511 public void requireResource(final String moduleCompleteKey, final Writer writer)
512 {
513 requireResource(moduleCompleteKey, writer, UrlMode.AUTO);
514 }
515
516 @Deprecated
517 public void setIncludeMode(final IncludeMode includeMode)
518 {
519 webResourceIntegration.getRequestCache().put("plugin.webresource.mode", includeMode);
520 }
521 }