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