1 package com.atlassian.plugin.webresource;
2
3 import static com.google.common.collect.Iterables.concat;
4 import static com.google.common.collect.Iterables.transform;
5
6 import org.apache.commons.collections.CollectionUtils;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9
10 import com.google.common.base.Function;
11 import com.google.common.collect.Iterables;
12
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.LinkedHashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24
25
26
27
28
29
30
31
32
33 class ContextBatchBuilder
34 {
35 private static final Logger log = LoggerFactory.getLogger(ContextBatchBuilder.class);
36
37 private final PluginResourceLocator pluginResourceLocator;
38 private final ResourceDependencyResolver dependencyResolver;
39 private final ResourceBatchingConfiguration batchingConfiguration;
40
41 private final List<String> allIncludedResources = new ArrayList<String>();
42 private final Set<String> skippedResources = new HashSet<String>();
43
44 ContextBatchBuilder(final PluginResourceLocator pluginResourceLocator, final ResourceDependencyResolver dependencyResolver, ResourceBatchingConfiguration batchingConfiguration)
45 {
46 this.pluginResourceLocator = pluginResourceLocator;
47 this.dependencyResolver = dependencyResolver;
48 this.batchingConfiguration = batchingConfiguration;
49 }
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 Iterable<PluginResource> build(final List<String> includedContexts)
67 {
68 return build(includedContexts, DefaultWebResourceFilter.INSTANCE);
69 }
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 Iterable<PluginResource> build(final Iterable<String> includedContexts, final WebResourceFilter filter)
87 {
88 if (!batchingConfiguration.isContextBatchingEnabled())
89 {
90 return getUnbatchedResources(includedContexts, null, filter);
91 }
92
93 final ContextBatchOperations contextBatchOperations = new ContextBatchOperations(pluginResourceLocator, filter);
94
95
96
97
98
99 final List<ContextBatch> batches = new ArrayList<ContextBatch>();
100
101 for (final String context : includedContexts)
102 {
103 final ContextBatch contextBatch = new ContextBatch(context, dependencyResolver.getDependenciesInContext(context, skippedResources));
104 final List<ContextBatch> mergeList = new ArrayList<ContextBatch>();
105 for (final WebResourceModuleDescriptor contextResource : contextBatch.getResources())
106 {
107
108 if (!allIncludedResources.contains(contextResource.getCompleteKey()))
109 {
110 for (final PluginResource pluginResource : pluginResourceLocator.getPluginResources(contextResource.getCompleteKey()))
111 {
112 if (filter.matches(pluginResource.getResourceName()))
113 {
114 contextBatch.addResourceType(pluginResource);
115 }
116 }
117
118 allIncludedResources.add(contextResource.getCompleteKey());
119 }
120 else
121 {
122
123
124 for (final ContextBatch batch : batches)
125 {
126 if (!mergeList.contains(batch) && batch.isResourceIncluded(contextResource.getCompleteKey()))
127 {
128 if (log.isDebugEnabled())
129 {
130 log.debug("Context: {} shares a resource with {}: {}", new String[] { context, batch.getKey(), contextResource.getCompleteKey() });
131 }
132
133 mergeList.add(batch);
134 }
135 }
136 }
137 }
138
139
140 if (!mergeList.isEmpty())
141 {
142 ContextBatch mergedBatch = mergeList.get(0);
143 batches.remove(mergedBatch);
144
145 for (int i = 1; i < mergeList.size(); i++)
146 {
147 final ContextBatch mergingBatch = mergeList.get(i);
148 mergedBatch = contextBatchOperations.merge(Arrays.asList(mergedBatch, mergingBatch));
149 batches.remove(mergingBatch);
150 }
151
152 mergedBatch = contextBatchOperations.merge(Arrays.asList(mergedBatch, contextBatch));
153 batches.add(mergedBatch);
154 }
155 else
156 {
157
158 batches.add(contextBatch);
159 }
160 }
161
162
163 return concat(transform(batches, new Function<ContextBatch, Iterable<PluginResource>>()
164 {
165 public Iterable<PluginResource> apply(final ContextBatch batch)
166 {
167 return batch.buildPluginResources();
168 }
169 }));
170 }
171
172 Iterable<PluginResource> build(final List<String> includedContexts, final List<String> excludedContexts)
173 {
174 return build(includedContexts, excludedContexts, DefaultWebResourceFilter.INSTANCE);
175 }
176
177 Iterable<PluginResource> build(final List<String> includedContexts, final List<String> excludedContexts, final WebResourceFilter filter)
178 {
179 if (batchingConfiguration.isContextBatchingEnabled())
180 {
181 return buildBatched(includedContexts, excludedContexts, filter);
182 }
183 else
184 {
185 return getUnbatchedResources(includedContexts, excludedContexts, filter);
186 }
187 }
188
189
190
191
192
193
194
195
196
197
198 private Iterable<PluginResource> buildBatched(final List<String> includedContexts, final List<String> excludedContexts, final WebResourceFilter filter)
199 {
200 Set<String> conditionalIncludedResources = new HashSet<String>();
201 WebResourceKeysToContextBatches includedBatches = WebResourceKeysToContextBatches.create(includedContexts, dependencyResolver, pluginResourceLocator, filter, conditionalIncludedResources);
202 WebResourceKeysToContextBatches excludedBatches = null;
203 if (excludedContexts != null && !Iterables.isEmpty(excludedContexts))
204 {
205 excludedBatches = WebResourceKeysToContextBatches.create(excludedContexts, dependencyResolver, pluginResourceLocator, filter, new HashSet<String>());
206 }
207
208 skippedResources.addAll(includedBatches.getSkippedResources());
209
210
211
212
213
214 final List<ContextBatch> batches = new ArrayList<ContextBatch>();
215
216
217 final List<ContextBatch> batchesToProcess = new ArrayList<ContextBatch>(includedBatches.getContextBatches());
218
219 final ContextBatchOperations contextBatchOperations = new ContextBatchOperations(pluginResourceLocator, filter);
220
221 while (!batchesToProcess.isEmpty())
222 {
223 ContextBatch contextBatch = batchesToProcess.remove(0);
224 Set<ContextBatch> alreadyProcessedBatches = new HashSet<ContextBatch>();
225 alreadyProcessedBatches.add(contextBatch);
226
227 Iterator<WebResourceModuleDescriptor> resourceIterator = contextBatch.getResources().iterator();
228 while (resourceIterator.hasNext())
229 {
230 WebResourceModuleDescriptor contextResource = resourceIterator.next();
231 String resourceKey = contextResource.getCompleteKey();
232
233 List<ContextBatch> additionalContexts = includedBatches.getAdditionalContextsForResourceKey(resourceKey, alreadyProcessedBatches);
234
235 if (CollectionUtils.isNotEmpty(additionalContexts))
236 {
237 if (log.isDebugEnabled())
238 {
239 for (ContextBatch additional : additionalContexts)
240 {
241 log.debug("Context: {} shares a resource with {}: {}", new String[] { contextBatch.getKey(), additional.getKey(), contextResource.getCompleteKey() });
242 }
243 }
244
245 List<ContextBatch> contextsToMerge = new ArrayList<ContextBatch>(1 + additionalContexts.size());
246 contextsToMerge.add(contextBatch);
247 contextsToMerge.addAll(additionalContexts);
248 contextBatch = contextBatchOperations.merge(contextsToMerge);
249
250
251 batchesToProcess.removeAll(additionalContexts);
252 alreadyProcessedBatches.addAll(additionalContexts);
253
254
255
256 resourceIterator = contextBatch.getResources().iterator();
257 }
258 }
259
260
261
262
263
264 if (excludedBatches != null)
265 {
266 resourceIterator = contextBatch.getResources().iterator();
267 while (resourceIterator.hasNext())
268 {
269 WebResourceModuleDescriptor contextResource = resourceIterator.next();
270 String resourceKey = contextResource.getCompleteKey();
271
272 List<ContextBatch> excludeContexts = excludedBatches.getContextsForResourceKey(resourceKey);
273 if (!excludeContexts.isEmpty())
274 {
275 contextBatch = contextBatchOperations.subtract(contextBatch, excludeContexts);
276 }
277 }
278
279 skippedResources.removeAll(excludedBatches.getSkippedResources());
280 }
281
282
283 if (excludedBatches == null || Iterables.size(contextBatch.getResources()) != 0)
284 {
285 Iterables.addAll(allIncludedResources, contextBatch.getResourceKeys());
286 batches.add(contextBatch);
287 }
288 else if (log.isDebugEnabled())
289 {
290 log.debug("The context batch {} contains no resources so will be dropped.", contextBatch.getKey());
291 }
292 }
293
294
295 return concat(transform(batches, new Function<ContextBatch, Iterable<PluginResource>>()
296 {
297 public Iterable<PluginResource> apply(final ContextBatch batch)
298 {
299 return batch.buildPluginResources();
300 }
301 }));
302 }
303
304
305 private Iterable<PluginResource> getUnbatchedResources(final Iterable<String> includedContexts, final Iterable<String> excludedContexts, final WebResourceFilter filter)
306 {
307 Set<String> excludedResourceKeys = new HashSet<String>();
308 Set<String> excludedSkippedResources = new HashSet<String>();
309
310 if (excludedContexts != null && Iterables.size(excludedContexts) > 0)
311 {
312 for (final String context : excludedContexts)
313 {
314 Iterable<WebResourceModuleDescriptor> contextResources = dependencyResolver.getDependenciesInContext(context, excludedSkippedResources);
315 for (final WebResourceModuleDescriptor contextResource : contextResources)
316 {
317 excludedResourceKeys.add(contextResource.getCompleteKey());
318 }
319 }
320 }
321
322 LinkedHashSet<PluginResource> includedResources = new LinkedHashSet<PluginResource>();
323 Set<String> includedSkippedResources = new HashSet<String>();
324
325 for (final String context : includedContexts)
326 {
327 Iterable<WebResourceModuleDescriptor> contextResources = dependencyResolver.getDependenciesInContext(context, includedSkippedResources);
328
329 for (final WebResourceModuleDescriptor contextResource : contextResources)
330 {
331 String completeKey = contextResource.getCompleteKey();
332 if (!excludedResourceKeys.contains(completeKey) && !allIncludedResources.contains(completeKey))
333 {
334 final List<PluginResource> moduleResources = pluginResourceLocator.getPluginResources(contextResource.getCompleteKey());
335 for (final PluginResource moduleResource : moduleResources)
336 {
337 if (filter.matches(moduleResource.getResourceName()))
338 {
339 includedResources.add(moduleResource);
340 }
341 }
342
343 allIncludedResources.add(contextResource.getCompleteKey());
344 }
345 }
346 }
347
348 includedSkippedResources.removeAll(excludedSkippedResources);
349 skippedResources.addAll(includedSkippedResources);
350
351 return includedResources;
352 }
353
354 Iterable<String> getAllIncludedResources()
355 {
356 return allIncludedResources;
357 }
358
359 Iterable<String> getSkippedResources()
360 {
361 return skippedResources;
362 }
363
364
365 private static class WebResourceKeysToContextBatches
366 {
367
368
369
370
371
372
373
374
375
376
377
378
379
380 static WebResourceKeysToContextBatches create(final List<String> contexts, final ResourceDependencyResolver dependencyResolver, PluginResourceLocator pluginResourceLocator, final WebResourceFilter filter, Set<String> conditionalResources)
381 {
382 final Map<String, List<ContextBatch>> resourceKeyToContext = new HashMap<String,List<ContextBatch>>();
383 final List<ContextBatch> batches = new ArrayList<ContextBatch>();
384 final Set<String> skippedResources = new HashSet<String>();
385
386 for (String context : contexts)
387 {
388 Iterable<WebResourceModuleDescriptor> dependencies = dependencyResolver.getDependenciesInContext(context, skippedResources);
389
390 ContextBatch batch = new ContextBatch(context, dependencies);
391 for (WebResourceModuleDescriptor moduleDescriptor : dependencies)
392 {
393 String key = moduleDescriptor.getCompleteKey();
394 boolean matchedPluginResource = false;
395
396 for (final PluginResource pluginResource : pluginResourceLocator.getPluginResources(moduleDescriptor.getCompleteKey()))
397 {
398 if (filter.matches(pluginResource.getResourceName()))
399 {
400 batch.addResourceType(pluginResource);
401 matchedPluginResource = true;
402 }
403 }
404
405 if (matchedPluginResource)
406 {
407 if (!resourceKeyToContext.containsKey(key))
408 {
409 resourceKeyToContext.put(key, new ArrayList<ContextBatch>());
410 }
411
412 resourceKeyToContext.get(key).add(batch);
413
414 if (!batches.contains(batch))
415 {
416 batches.add(batch);
417 }
418 }
419 }
420
421 }
422
423 return new WebResourceKeysToContextBatches(resourceKeyToContext, batches, skippedResources);
424 }
425
426 private final Map<String, List<ContextBatch>> resourceToContextBatches;
427 private final List<ContextBatch> knownBatches;
428 private final Set<String> skippedResources;
429
430 private WebResourceKeysToContextBatches(Map<String, List<ContextBatch>> resourceKeyToContext, List<ContextBatch> allBatches, Set<String> skippedResources)
431 {
432 this.resourceToContextBatches = resourceKeyToContext;
433 this.knownBatches = allBatches;
434 this.skippedResources = skippedResources;
435 }
436
437
438
439
440
441 List<ContextBatch> getContextsForResourceKey(String key)
442 {
443 return getAdditionalContextsForResourceKey(key, null);
444 }
445
446
447
448
449
450
451
452 List<ContextBatch> getAdditionalContextsForResourceKey(String key, Collection<ContextBatch> knownContexts)
453 {
454 List<ContextBatch> allContexts = resourceToContextBatches.get(key);
455 if (CollectionUtils.isEmpty(allContexts))
456 {
457 return Collections.emptyList();
458 }
459
460 LinkedHashSet<ContextBatch> contexts = new LinkedHashSet<ContextBatch>(allContexts);
461 if (CollectionUtils.isNotEmpty(knownContexts))
462 {
463 contexts.removeAll(knownContexts);
464 }
465
466 return new ArrayList<ContextBatch>(contexts);
467 }
468
469
470
471
472 List<ContextBatch> getContextBatches()
473 {
474 return new ArrayList<ContextBatch>(knownBatches);
475 }
476
477
478
479
480 public Set<String> getSkippedResources()
481 {
482 return skippedResources;
483 }
484 }
485 }