View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import static com.google.common.base.Preconditions.checkNotNull;
4   import static com.google.common.collect.Iterables.contains;
5   import static java.util.Collections.emptyMap;
6   import static java.util.Collections.unmodifiableCollection;
7   import static java.util.Collections.unmodifiableMap;
8   
9   import com.atlassian.plugin.ModuleDescriptor;
10  import com.atlassian.util.concurrent.ResettableLazyReference;
11  
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import com.google.common.collect.Iterables;
16  
17  import java.util.Collections;
18  import java.util.LinkedHashMap;
19  import java.util.LinkedHashSet;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.Stack;
24  
25  class DefaultResourceDependencyResolver implements ResourceDependencyResolver
26  {
27      private static final Logger log = LoggerFactory.getLogger(DefaultResourceDependencyResolver.class);
28  
29      private final WebResourceIntegration webResourceIntegration;
30      private final ResourceBatchingConfiguration batchingConfiguration;
31      private final Cache cached = new Cache();
32  
33      public DefaultResourceDependencyResolver(final WebResourceIntegration webResourceIntegration, final ResourceBatchingConfiguration batchingConfiguration)
34      {
35          this.webResourceIntegration = webResourceIntegration;
36          this.batchingConfiguration = batchingConfiguration;
37      }
38  
39      public Iterable<WebResourceModuleDescriptor> getSuperBatchDependencies()
40      {
41          return cached.resourceMap().values();
42      }
43  
44      private Iterable<String> getSuperBatchDependencyKeys()
45      {
46          return cached.resourceMap().keySet();
47      }
48  
49      public Iterable<WebResourceModuleDescriptor> getDependencies(final String moduleKey, final boolean excludeSuperBatchedResources)
50      {
51          final LinkedHashMap<String, WebResourceModuleDescriptor> orderedResources = new LinkedHashMap<String, WebResourceModuleDescriptor>();
52          final Iterable<String> superBatchResources = excludeSuperBatchedResources ? getSuperBatchDependencyKeys() : Collections.<String> emptyList();
53          resolveDependencies(moduleKey, orderedResources, superBatchResources, new Stack<String>(), null);
54          return orderedResources.values();
55      }
56  
57      public Iterable<WebResourceModuleDescriptor> getDependenciesInContext(final String context)
58      {
59          return getDependenciesInContext(context, new LinkedHashSet<String>());
60      }
61  
62      public Iterable<WebResourceModuleDescriptor> getDependenciesInContext(final String context, final Set<String> skippedResources)
63      {
64          final Set<WebResourceModuleDescriptor> contextResources = new LinkedHashSet<WebResourceModuleDescriptor>();
65          final Class<WebResourceModuleDescriptor> clazz = WebResourceModuleDescriptor.class;
66          for (final WebResourceModuleDescriptor descriptor : webResourceIntegration.getPluginAccessor().getEnabledModuleDescriptorsByClass(clazz))
67          {
68              if (descriptor.getContexts().contains(context))
69              {
70                  final LinkedHashMap<String, WebResourceModuleDescriptor> dependencies = new LinkedHashMap<String, WebResourceModuleDescriptor>();
71                  resolveDependencies(descriptor.getCompleteKey(), dependencies, getSuperBatchDependencyKeys(), new Stack<String>(), skippedResources);
72                  for (final WebResourceModuleDescriptor dependency : dependencies.values())
73                  {
74                      contextResources.add(dependency);
75                  }
76              }
77          }
78          return unmodifiableCollection(contextResources);
79      }
80  
81      /**
82       * Adds the resources as well as its dependencies in order to the given ordered set. This method uses recursion
83       * to add a resouce's dependent resources also to the set. You should call this method with a new stack
84       * passed to the last parameter.
85       *
86       * Note that resources already in the given super batch will be excluded when resolving dependencies. You
87       * should pass in an empty set for the super batch to include super batch resources.
88       *
89       * @param moduleKey the module complete key to add as well as its dependencies
90       * @param orderedResourceKeys an ordered list set where the resources are added in order
91       * @param superBatchResources the set of super batch resources to exclude when resolving dependencies
92       * @param stack where we are in the dependency tree
93       * @param skippedResources if not null, all resources with conditions are skipped and added to this set.
94       */
95      private void resolveDependencies(final String moduleKey, final Map<String, WebResourceModuleDescriptor> orderedResourceKeys, final Iterable<String> superBatchResources, final Stack<String> stack, final Set<String> skippedResources)
96      {
97          if (contains(superBatchResources, moduleKey))
98          {
99              log.debug("Not requiring resource: {} because it is part of a super-batch", moduleKey);
100             return;
101         }
102         if (stack.contains(moduleKey))
103         {
104             log.warn("Cyclic plugin resource dependency has been detected with: {} \nStack trace: {}", moduleKey, stack);
105             return;
106         }
107 
108         final ModuleDescriptor<?> moduleDescriptor = webResourceIntegration.getPluginAccessor().getEnabledPluginModule(moduleKey);
109         if (!(moduleDescriptor instanceof WebResourceModuleDescriptor))
110         {
111             if (webResourceIntegration.getPluginAccessor().getPluginModule(moduleKey) != null)
112             {
113                 log.warn("Cannot include disabled web resource module: " + moduleKey);
114             }
115             else
116             {
117                 log.warn("Cannot find web resource module for: " + moduleKey);
118             }
119             return;
120         }
121 
122         final WebResourceModuleDescriptor webResourceModuleDescriptor = (WebResourceModuleDescriptor) moduleDescriptor;
123 
124         if ((skippedResources != null) && (webResourceModuleDescriptor.getCondition() != null))
125         {
126             skippedResources.add(moduleKey);
127             return;
128         }
129         else if (!webResourceModuleDescriptor.shouldDisplay())
130         {
131             log.debug("Cannot include web resource module {} as its condition fails", moduleDescriptor.getCompleteKey());
132             return;
133         }
134 
135         final List<String> dependencies = webResourceModuleDescriptor.getDependencies();
136         if (log.isDebugEnabled())
137         {
138             log.debug("About to add resource [{}] and its dependencies: {}", moduleKey, dependencies);
139         }
140         stack.push(moduleKey);
141         try
142         {
143             for (final String dependency : dependencies)
144             {
145                 if (orderedResourceKeys.get(dependency) == null)
146                 {
147                     resolveDependencies(dependency, orderedResourceKeys, superBatchResources, stack, skippedResources);
148                 }
149             }
150         }
151         finally
152         {
153             stack.pop();
154         }
155         orderedResourceKeys.put(moduleKey, webResourceModuleDescriptor);
156     }
157 
158     final class Cache
159     {
160         ResettableLazyReference<SuperBatch> lazy = new ResettableLazyReference<SuperBatch>()
161         {
162             @Override
163             protected SuperBatch create() throws Exception
164             {
165                 // The linked hash map ensures that order is preserved
166                 final String version = webResourceIntegration.getSuperBatchVersion();
167                 return new SuperBatch(version, loadDescriptors(batchingConfiguration.getSuperBatchModuleCompleteKeys()));
168             }
169 
170             Map<String, WebResourceModuleDescriptor> loadDescriptors(final Iterable<String> moduleKeys)
171             {
172                 if (Iterables.isEmpty(moduleKeys))
173                 {
174                     return emptyMap();
175                 }
176                 final Map<String, WebResourceModuleDescriptor> resources = new LinkedHashMap<String, WebResourceModuleDescriptor>();
177                 for (final String moduleKey : moduleKeys)
178                 {
179                     resolveDependencies(moduleKey, resources, Collections.<String> emptyList(), new Stack<String>(), null);
180                 }
181                 return unmodifiableMap(resources);
182             }
183         };
184 
185         Map<String, WebResourceModuleDescriptor> resourceMap()
186         {
187             if (!batchingConfiguration.isSuperBatchingEnabled())
188             {
189                 log.debug("Super batching not enabled, but getSuperBatchDependencies() called. Returning empty.");
190                 return emptyMap();
191             }
192             while (true)
193             {
194                 final SuperBatch batch = lazy.get();
195                 if (batch.version.equals(webResourceIntegration.getSuperBatchVersion()))
196                 {
197                     return batch.resources;
198                 }
199 
200                 // The super batch has been updated so recreate the batch
201                 lazy.reset();
202             }
203         }
204     }
205 
206     static final class SuperBatch
207     {
208         final String version;
209         final Map<String, WebResourceModuleDescriptor> resources;
210 
211         SuperBatch(final String version, final Map<String, WebResourceModuleDescriptor> resources)
212         {
213             this.version = checkNotNull(version);
214             this.resources = checkNotNull(resources);
215         }
216     }
217 }