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.Iterables;
15 import com.google.common.collect.Lists;
16 import com.google.common.collect.Sets;
17
18 import java.io.IOException;
19 import java.io.StringWriter;
20 import java.io.Writer;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
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().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(new ArrayList<String>(getIncludedContexts()), 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 protected String getResourceTagsForAdditionalContexts(final List<String> contexts)
293 {
294 return getResourceTagsForAdditionalContexts(contexts, UrlMode.AUTO, DefaultWebResourceFilter.INSTANCE);
295 }
296
297 protected String getResourceTagsForAdditionalContexts(final List<String> contexts, final UrlMode urlMode, final WebResourceFilter filter)
298 {
299 final ContextBatchBuilder builder = new ContextBatchBuilder(pluginResourceLocator, dependencyResolver, batchingConfiguration);
300 final Iterable<PluginResource> resourcesToInclude = builder.build(contexts, new ArrayList<String>(getIncludedContexts()), filter);
301
302 final boolean batchingEnabled = batchingConfiguration.isSuperBatchingEnabled();
303 Set<String> moduleKeys = new HashSet<String>();
304
305 for (final String skippedResource : builder.getSkippedResources())
306 {
307 Iterables.addAll(moduleKeys, toModuleKeys(dependencyResolver.getDependencies(skippedResource, batchingEnabled)));
308 }
309
310 final Iterable<PluginResource> moduleResources = getModuleResources(moduleKeys, builder.getAllIncludedResources(), filter);
311 return writeResourceTags(concat(resourcesToInclude, moduleResources), new StringWriter(), urlMode).toString();
312 }
313
314 public String getStaticResourcePrefix(final String resourceCounter, final UrlMode urlMode)
315 {
316 return webResourceUrlProvider.getStaticResourcePrefix(resourceCounter, urlMode);
317 }
318
319 public String getStaticPluginResource(final String moduleCompleteKey, final String resourceName, final UrlMode urlMode)
320 {
321 return webResourceUrlProvider.getStaticPluginResourceUrl(moduleCompleteKey, resourceName, urlMode);
322 }
323
324
325
326
327 public String getStaticPluginResource(final ModuleDescriptor<?> moduleDescriptor, final String resourceName, final UrlMode urlMode)
328 {
329 return webResourceUrlProvider.getStaticPluginResourceUrl(moduleDescriptor, resourceName, urlMode);
330 }
331
332 public <T> T executeInNewContext(final Supplier<T> nestedExecution)
333 {
334 final Map<String, Object> cache = webResourceIntegration.getRequestCache();
335 final Map<String, Object> storedState = copyOf(cache);
336
337
338 cache.clear();
339 try
340 {
341 return nestedExecution.get();
342 }
343 finally
344 {
345
346
347
348 cache.clear();
349 cache.putAll(storedState);
350 }
351 }
352
353
354
355
356
357 private Iterable<PluginResource> getModuleResources(final Iterable<String> webResourcePluginModuleKeys, final Iterable<String> batchedModules, final WebResourceFilter filter)
358 {
359 final List<PluginResource> includedResources = new LinkedList<PluginResource>();
360 for (final String moduleKey : webResourcePluginModuleKeys)
361 {
362 if (contains(batchedModules, moduleKey))
363 {
364
365 continue;
366 }
367
368 final List<PluginResource> moduleResources = pluginResourceLocator.getPluginResources(moduleKey);
369 for (final PluginResource moduleResource : moduleResources)
370 {
371 if (filter.matches(moduleResource.getResourceName()))
372 {
373 includedResources.add(moduleResource);
374 }
375 }
376 }
377 return includedResources;
378 }
379
380 private void writeContentAndSwallowErrors(final Writer writer, final String... contents)
381 {
382 try
383 {
384 for (final String content : contents)
385 {
386 writer.write(content);
387 }
388 }
389 catch (final IOException ignore)
390 {}
391 }
392
393 private LinkedHashSet<String> getIncludedContexts()
394 {
395 return getOrCreateFromRequestCache(REQUEST_CACHE_CONTEXT_KEY);
396 }
397
398 private LinkedHashSet<String> getIncludedResourceNames()
399 {
400 return getOrCreateFromRequestCache(REQUEST_CACHE_RESOURCE_KEY);
401 }
402
403 private LinkedHashSet<String> getOrCreateFromRequestCache(final String key)
404 {
405 final Map<String, Object> cache = webResourceIntegration.getRequestCache();
406 @SuppressWarnings("unchecked")
407 LinkedHashSet<String> set = (LinkedHashSet<String>) cache.get(key);
408 if (set == null)
409 {
410 set = Sets.newLinkedHashSet();
411 cache.put(key, set);
412 }
413 return set;
414 }
415
416 private void clear()
417 {
418 getIncludedResourceNames().clear();
419 getIncludedContexts().clear();
420 }
421
422 private Iterable<String> toModuleKeys(final Iterable<WebResourceModuleDescriptor> descriptors)
423 {
424 return transform(descriptors, new TransformDescriptorToKey());
425 }
426
427
428
429
430
431 @Deprecated
432 public String getRequiredResources()
433 {
434 return getRequiredResources(UrlMode.AUTO);
435 }
436
437 @Deprecated
438 public String getResourceTags(final String moduleCompleteKey)
439 {
440 return getResourceTags(moduleCompleteKey, UrlMode.AUTO);
441 }
442
443 @Deprecated
444 public String getStaticPluginResource(final ModuleDescriptor<?> moduleDescriptor, final String resourceName)
445 {
446 return getStaticPluginResource(moduleDescriptor, resourceName, UrlMode.AUTO);
447 }
448
449 @Deprecated
450 public String getStaticPluginResource(final String moduleCompleteKey, final String resourceName)
451 {
452 return getStaticPluginResource(moduleCompleteKey, resourceName, UrlMode.AUTO);
453 }
454
455 @Deprecated
456 public String getStaticPluginResourcePrefix(final ModuleDescriptor<?> moduleDescriptor, final String resourceName)
457 {
458 return getStaticPluginResource(moduleDescriptor, resourceName, UrlMode.AUTO);
459 }
460
461 @Deprecated
462 public String getStaticResourcePrefix()
463 {
464 return webResourceUrlProvider.getStaticResourcePrefix(UrlMode.AUTO);
465 }
466
467 @Deprecated
468 public String getStaticResourcePrefix(final UrlMode urlMode)
469 {
470 return webResourceUrlProvider.getStaticResourcePrefix(urlMode);
471 }
472
473 @Deprecated
474 public String getStaticResourcePrefix(final String resourceCounter)
475 {
476 return getStaticResourcePrefix(resourceCounter, UrlMode.AUTO);
477 }
478
479 @Deprecated
480 public void includeResources(final Writer writer)
481 {
482 includeResources(writer, UrlMode.AUTO);
483 }
484
485 @Deprecated
486 public void requireResource(final String moduleCompleteKey, final Writer writer)
487 {
488 requireResource(moduleCompleteKey, writer, UrlMode.AUTO);
489 }
490
491 @Deprecated
492 public void setIncludeMode(final IncludeMode includeMode)
493 {
494 webResourceIntegration.getRequestCache().put("plugin.webresource.mode", includeMode);
495 }
496 }