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