View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import static com.atlassian.plugin.webresource.AbstractBatchResourceBuilder.skipBatch;
4   
5   import com.atlassian.plugin.ModuleDescriptor;
6   import com.atlassian.plugin.PluginAccessor;
7   import com.atlassian.plugin.cache.filecache.FileCache;
8   import com.atlassian.plugin.cache.filecache.impl.FileCacheImpl;
9   import com.atlassian.plugin.cache.filecache.impl.PassThroughFileCache;
10  import com.atlassian.plugin.elements.ResourceDescriptor;
11  import com.atlassian.plugin.event.PluginEventListener;
12  import com.atlassian.plugin.event.PluginEventManager;
13  import com.atlassian.plugin.event.events.PluginDisabledEvent;
14  import com.atlassian.plugin.event.events.PluginEnabledEvent;
15  import com.atlassian.plugin.servlet.DownloadableResource;
16  import com.atlassian.plugin.servlet.ServletContextFactory;
17  import com.atlassian.plugin.util.PluginUtils;
18  import com.atlassian.plugin.webresource.cache.FileCacheKey;
19  import com.atlassian.util.concurrent.LazyReference;
20  import com.google.common.base.Supplier;
21  import com.google.common.collect.ImmutableList;
22  import org.apache.commons.lang.StringUtils;
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.TreeMap;
35  import java.util.concurrent.atomic.AtomicLong;
36  
37  /**
38   * Default implementation of {@link PluginResourceLocator}.
39   *
40   * @since 2.2
41   */
42  public class PluginResourceLocatorImpl implements PluginResourceLocator
43  {
44      private static final Logger log = LoggerFactory.getLogger(PluginResourceLocatorImpl.class);
45  
46      final private PluginAccessor pluginAccessor;
47      final private WebResourceUrlProvider webResourceUrlProvider;
48      final private ResourceBatchingConfiguration batchingConfiguration;
49      final private List<DownloadableResourceBuilder> builders;
50      private final FileCache<FileCacheKey> fileCache;
51  
52      /**
53       * This field is static to allow multiple instances of {@link FileCacheImpl} in the system to use the same tmp dir,
54       * without overwriting each other's files.
55       */
56      private static final AtomicLong FILENAME_COUNTER = new AtomicLong(0);
57  
58      static final String RESOURCE_SOURCE_PARAM = "source";
59      static final String RESOURCE_BATCH_PARAM = "batch";
60      /** there can easily be 50-80 in a "normal" app, make it 20x bigger */
61      static final int DEFAULT_CACHE_SIZE = 1000;
62  
63      public PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration, final ServletContextFactory servletContextFactory, final WebResourceUrlProvider webResourceUrlProvider, final PluginEventManager pluginEventManager)
64      {
65          this(webResourceIntegration, servletContextFactory, webResourceUrlProvider, new DefaultResourceBatchingConfiguration(), pluginEventManager);
66      }
67  
68      public PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration,
69                                       final ServletContextFactory servletContextFactory,
70                                       final WebResourceUrlProvider webResourceUrlProvider,
71                                       final ResourceBatchingConfiguration batchingConfiguration, final PluginEventManager pluginEventManager)
72      {
73          this(webResourceIntegration, servletContextFactory, webResourceUrlProvider, new DefaultResourceDependencyResolver(webResourceIntegration, batchingConfiguration), batchingConfiguration, pluginEventManager);
74      }
75  
76      private PluginResourceLocatorImpl(final WebResourceIntegration webResourceIntegration, final ServletContextFactory servletContextFactory, final WebResourceUrlProvider webResourceUrlProvider,
77                                        final ResourceDependencyResolver dependencyResolver, final ResourceBatchingConfiguration batchingConfiguration,
78                                        final PluginEventManager pluginEventManager)
79      {
80          this.pluginAccessor = webResourceIntegration.getPluginAccessor();
81          this.webResourceUrlProvider = webResourceUrlProvider;
82          this.batchingConfiguration = batchingConfiguration;
83          final SingleDownloadableResourceBuilder singlePluginBuilder =
84                  new SingleDownloadableResourceBuilder(pluginAccessor, servletContextFactory, webResourceIntegration);
85  
86          FileCache<FileCacheKey> cache = makePhysicalCache(webResourceIntegration);
87          if (cache == null) {
88              cache = PassThroughFileCache.instance();
89          }
90  
91          this.fileCache = cache;
92  
93          builders = Collections.unmodifiableList(Arrays.asList(
94                  new SuperBatchDownloadableResourceBuilder(dependencyResolver, pluginAccessor, webResourceUrlProvider, singlePluginBuilder, cache),
95                  new SuperBatchSubResourceBuilder(dependencyResolver, singlePluginBuilder),
96                  new ContextBatchDownloadableResourceBuilder(dependencyResolver, pluginAccessor, webResourceUrlProvider, singlePluginBuilder, cache),
97                  new ContextBatchSubResourceBuilder(dependencyResolver, singlePluginBuilder),
98                  new SingleBatchDownloadableResourceBuilder(pluginAccessor, webResourceUrlProvider, singlePluginBuilder, cache),
99                  singlePluginBuilder));
100 
101         pluginEventManager.register(this);
102     }
103 
104     private static FileCache<FileCacheKey> makePhysicalCache(WebResourceIntegration webResourceIntegration) {
105 
106         boolean cacheDisabled = Boolean.getBoolean(PluginUtils.WEBRESOURCE_DISABLE_FILE_CACHE) || Boolean.getBoolean(PluginUtils.ATLASSIAN_DEV_MODE);
107         if (cacheDisabled) {
108             return null;
109         }
110 
111 
112         FileCache<FileCacheKey> cache = null;
113         try {
114             int cachesize = Integer.getInteger(PluginUtils.WEBRESOURCE_FILE_CACHE_SIZE, DEFAULT_CACHE_SIZE);
115             final File tmpDir = webResourceIntegration.getTemporaryDirectory();
116             if (tmpDir != null) {
117                 cache = new FileCacheImpl<FileCacheKey>(tmpDir, cachesize, FILENAME_COUNTER);
118             }
119         } catch (IOException e) {
120             log.error("Could not create file cache object, will startup with filecaching disabled, please investigate the cause and correct it.", e);
121         }
122         return cache;
123     }
124 
125     @PluginEventListener
126     public void onPluginDisabled(final PluginDisabledEvent event)
127     {
128         fileCache.clear();
129     }
130 
131     @PluginEventListener
132     public void onPluginEnabled(final PluginEnabledEvent event)
133     {
134         fileCache.clear();
135     }
136 
137     public boolean matches(final String url)
138     {
139         for (final DownloadableResourceBuilder builder : builders)
140         {
141             if (builder.matches(url))
142             {
143                 return true;
144             }
145         }
146 
147         return false;
148     }
149 
150     public DownloadableResource getDownloadableResource(final String url, final Map<String, String> queryParams)
151     {
152         try
153         {
154             for (final DownloadableResourceBuilder builder : builders)
155             {
156                 if (builder.matches(url))
157                 {
158                     return builder.parse(url, queryParams);
159                 }
160             }
161         }
162         catch (final UrlParseException e)
163         {
164             log.error("Unable to parse URL: " + url, e);
165         }
166         // TODO: It would be better to use Exceptions rather than returning
167         // nulls to indicate an error.
168         return null;
169     }
170 
171     public List<PluginResource> getPluginResources(final String moduleCompleteKey)
172     {
173         final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(moduleCompleteKey);
174         if ((moduleDescriptor == null) || !(moduleDescriptor instanceof WebResourceModuleDescriptor))
175         {
176             log.error("Error loading resource \"" + moduleCompleteKey + "\". Resource is not a Web Resource Module");
177             return Collections.emptyList();
178         }
179 
180         final boolean singleMode = !batchingConfiguration.isPluginWebResourceBatchingEnabled();
181         final Set<PluginResource> resources = new LinkedHashSet<PluginResource>();
182 
183         for (final ResourceDescriptor resourceDescriptor : moduleDescriptor.getResourceDescriptors())
184         {
185             if (singleMode || skipBatch(resourceDescriptor))
186             {
187                 final boolean cache = !"false".equalsIgnoreCase(resourceDescriptor.getParameter("cache"));
188                 resources.add(new SinglePluginResource(resourceDescriptor.getName(), moduleDescriptor.getCompleteKey(),
189                     cache,
190                     resourceDescriptor.getParameters()));
191             }
192             else
193             {
194                 final BatchPluginResource batchResource = createBatchResource(moduleDescriptor.getCompleteKey(), resourceDescriptor,
195                         new BatchedWebResourceDescriptor(moduleDescriptor.getPlugin().getPluginInformation().getVersion(), moduleDescriptor.getCompleteKey()));
196                 resources.add(batchResource);
197             }
198         }
199         return ImmutableList.copyOf(resources);
200     }
201 
202     // package protected so we can test it
203     String[] splitLastPathPart(final String resourcePath)
204     {
205         int indexOfSlash = resourcePath.lastIndexOf('/');
206         if (resourcePath.endsWith("/")) // skip over the trailing slash
207         {
208             indexOfSlash = resourcePath.lastIndexOf('/', indexOfSlash - 1);
209         }
210 
211         if (indexOfSlash < 0)
212         {
213             return null;
214         }
215 
216         return new String[] {resourcePath.substring(0, indexOfSlash + 1), resourcePath.substring(indexOfSlash + 1)};
217     }
218 
219     private BatchPluginResource createBatchResource(final String moduleCompleteKey,
220                                                     final ResourceDescriptor resourceDescriptor,
221                                                     final BatchedWebResourceDescriptor batchedWebResourceDescriptor)
222     {
223         final Map<String, String> params = new TreeMap<String, String>();
224         for (final String param : BATCH_PARAMS)
225         {
226             final String value = resourceDescriptor.getParameter(param);
227             if (StringUtils.isNotEmpty(value))
228             {
229                 params.put(param, value);
230             }
231         }
232 
233         final String name = resourceDescriptor.getName();
234         final Supplier<String> type = new LazyReference<String>()
235         {
236             @Override
237             protected String create()
238             {
239                 return name.substring(name.lastIndexOf(".") + 1);
240             }
241         };
242 
243         return new BatchPluginResource(ResourceKey.Builder.lazy(moduleCompleteKey, type), params, batchedWebResourceDescriptor);
244     }
245 
246     public String getResourceUrl(final String moduleCompleteKey, final String resourceName)
247     {
248         return webResourceUrlProvider.getResourceUrl(moduleCompleteKey, resourceName);
249     }
250 }