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
39
40
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
54
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
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
167
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
203 String[] splitLastPathPart(final String resourcePath)
204 {
205 int indexOfSlash = resourcePath.lastIndexOf('/');
206 if (resourcePath.endsWith("/"))
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 }