View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import java.util.Map;
4   
5   import com.atlassian.plugin.ModuleDescriptor;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginAccessor;
8   import com.atlassian.plugin.elements.ResourceLocation;
9   import com.atlassian.plugin.servlet.DownloadableClasspathResource;
10  import com.atlassian.plugin.servlet.DownloadableResource;
11  import com.atlassian.plugin.servlet.DownloadableWebResource;
12  import com.atlassian.plugin.servlet.ForwardableResource;
13  import com.atlassian.plugin.servlet.ServletContextFactory;
14  
15  import org.apache.commons.lang.text.StrSubstitutor;
16  import org.slf4j.Logger;
17  import org.slf4j.LoggerFactory;
18  
19  import static com.atlassian.plugin.webresource.PluginResourceLocatorImpl.RESOURCE_SOURCE_PARAM;
20  import static com.atlassian.plugin.webresource.SinglePluginResource.URL_PREFIX;
21  
22  /**
23   * Constructs a single resource for download
24   * @since 2.9.0
25   */
26  public class SingleDownloadableResourceBuilder implements DownloadableResourceBuilder, DownloadableResourceFinder
27  {
28      private static final String DOWNLOAD_TYPE = "download";
29      private static final Logger log = LoggerFactory.getLogger(SingleDownloadableResourceBuilder.class);
30  
31      private final PluginAccessor pluginAccessor;
32      private final ServletContextFactory servletContextFactory;
33      private final WebResourceIntegration webResourceIntegration;
34  
35      public SingleDownloadableResourceBuilder(final PluginAccessor pluginAccessor, final ServletContextFactory servletContextFactory,
36                                               final WebResourceIntegration webResourceIntegration)
37      {
38          this.pluginAccessor = pluginAccessor;
39          this.servletContextFactory = servletContextFactory;
40          this.webResourceIntegration = webResourceIntegration;
41      }
42  
43      public boolean matches(final String path)
44      {
45          return path.indexOf(URL_PREFIX) != -1;
46      }
47  
48      public DownloadableResource parse(final String path, final Map<String, String> params) throws UrlParseException
49      {
50          final int indexOfPrefix = path.indexOf(URL_PREFIX);
51          String libraryAndResource = path.substring(indexOfPrefix + URL_PREFIX.length() + 1);
52  
53          if (libraryAndResource.indexOf('?') != -1) // remove query parameters
54          {
55              libraryAndResource = libraryAndResource.substring(0, libraryAndResource.indexOf('?'));
56          }
57  
58          final String[] parts = libraryAndResource.split("/", 2);
59  
60          if (parts.length != 2)
61          {
62              throw new UrlParseException("Could not parse invalid plugin resource url: " + path);
63          }
64  
65          final PluginResource resource = new SinglePluginResource(parts[1], parts[0], path.substring(0, indexOfPrefix).length() > 0);
66  
67          return find(resource.getModuleCompleteKey(), resource.getResourceName());
68      }
69  
70      public DownloadableResource find(final String moduleKey, final String resourceName)
71      {
72          return locatePluginResource(moduleKey, resourceName);
73      }
74  
75      private DownloadableResource locatePluginResource(final String moduleCompleteKey, final String resourceName)
76      {
77          DownloadableResource resource;
78  
79          // resource from the module
80          if (moduleCompleteKey.indexOf(":") > -1)
81          {
82              final ModuleDescriptor<?> moduleDescriptor = pluginAccessor.getEnabledPluginModule(moduleCompleteKey);
83              if (moduleDescriptor != null)
84              {
85                  resource = getResourceFromModule(moduleDescriptor, resourceName, "");
86              }
87              else
88              {
89                  log.info("Module not found: " + moduleCompleteKey);
90                  return null;
91              }
92          }
93          else
94          // resource from plugin
95          {
96              resource = getResourceFromPlugin(pluginAccessor.getPlugin(moduleCompleteKey), resourceName, "");
97          }
98  
99          if (resource == null)
100         {
101             resource = getResourceFromPlugin(getPlugin(moduleCompleteKey), resourceName, "");
102         }
103 
104         if (resource == null)
105         {
106             log.info("Unable to find resource for plugin: " + moduleCompleteKey + " and path: " + resourceName);
107             return null;
108         }
109 
110         return resource;
111     }
112 
113     private Plugin getPlugin(final String moduleKey)
114     {
115         final int semicolonIndex = moduleKey.indexOf(':');
116         if ((semicolonIndex < 0) || (semicolonIndex == moduleKey.length() - 1))
117         {
118             return null;
119         }
120 
121         return pluginAccessor.getPlugin(moduleKey.substring(0, semicolonIndex));
122     }
123 
124     private DownloadableResource getResourceFromModule(final ModuleDescriptor<?> moduleDescriptor, final String resourcePath, final String filePath)
125     {
126         final Plugin plugin = pluginAccessor.getPlugin(moduleDescriptor.getPluginKey());
127         final ResourceLocation resourceLocation = moduleDescriptor.getResourceLocation(DOWNLOAD_TYPE, resourcePath);
128 
129         if (resourceLocation != null)
130         {
131             boolean disableMinification = false;
132             // I think it should always be a WebResourceModuleDescriptor, but
133             // not sure...
134             if (moduleDescriptor instanceof WebResourceModuleDescriptor)
135             {
136                 disableMinification = ((WebResourceModuleDescriptor) moduleDescriptor).isDisableMinification();
137             }
138             return getDownloadablePluginResource(plugin, resourceLocation, moduleDescriptor, filePath, disableMinification);
139         }
140 
141         final String[] nextParts = splitLastPathPart(resourcePath);
142         if (nextParts == null)
143         {
144             return null;
145         }
146 
147         return getResourceFromModule(moduleDescriptor, nextParts[0], nextParts[1] + filePath);
148     }
149 
150     private DownloadableResource getResourceFromPlugin(final Plugin plugin, final String resourcePath, final String filePath)
151     {
152         if (plugin == null)
153         {
154             return null;
155         }
156 
157         final ResourceLocation resourceLocation = plugin.getResourceLocation(DOWNLOAD_TYPE, resourcePath);
158         if (resourceLocation != null)
159         {
160             return getDownloadablePluginResource(plugin, resourceLocation, null, filePath, false);
161         }
162 
163         final String[] nextParts = splitLastPathPart(resourcePath);
164         if (nextParts == null)
165         {
166             return null;
167         }
168 
169         return getResourceFromPlugin(plugin, nextParts[0], nextParts[1] + filePath);
170     }
171 
172     private DownloadableResource getDownloadablePluginResource(final Plugin plugin, final ResourceLocation originalResourceLocation, final ModuleDescriptor<?> descriptor, final String filePath, final boolean disableMinification)
173     {
174         ResourceLocation resourceLocation = substituteVariables(originalResourceLocation);
175         final String sourceParam = resourceLocation.getParameter(RESOURCE_SOURCE_PARAM);
176 
177         // serve by forwarding the request to the location - batching not
178         // supported
179         if ("webContext".equalsIgnoreCase(sourceParam))
180         {
181             return new ForwardableResource(resourceLocation);
182         }
183 
184         DownloadableResource actualResource;
185         // serve static resources from the web application - batching supported
186         if ("webContextStatic".equalsIgnoreCase(sourceParam))
187         {
188             actualResource = new DownloadableWebResource(plugin, resourceLocation, filePath, servletContextFactory.getServletContext(),
189                 disableMinification);
190         }
191         else
192         {
193             actualResource = new DownloadableClasspathResource(plugin, resourceLocation, filePath);
194         }
195 
196         DownloadableResource result = actualResource;
197         // web resources are able to be transformed during delivery
198         if (descriptor instanceof WebResourceModuleDescriptor)
199         {
200             DownloadableResource lastResource = actualResource;
201             final WebResourceModuleDescriptor desc = (WebResourceModuleDescriptor) descriptor;
202             for (final WebResourceTransformation list : desc.getTransformations())
203             {
204                 if (list.matches(resourceLocation))
205                 {
206                     lastResource = list.transformDownloadableResource(pluginAccessor, actualResource, resourceLocation, filePath);
207                 }
208             }
209             result = lastResource;
210         }
211         return result;
212     }
213 
214     private ResourceLocation substituteVariables(ResourceLocation in)
215     {
216         String location = in.getLocation();
217         if (!location.contains("$"))
218         {
219             return in;
220         }
221 
222         String newLocation = substituteVariables(location);
223         return new ResourceLocation(newLocation, in.getName(), in.getType(), in.getContentType(), in.getContent(), in.getParams());
224     }
225 
226     private String substituteVariables(String location)
227     {
228         return StrSubstitutor.replace(location,
229                 webResourceIntegration.getResourceSubstitutionVariables());
230     }
231 
232     // pacakge protected so we can test it
233     String[] splitLastPathPart(final String resourcePath)
234     {
235         int indexOfSlash = resourcePath.lastIndexOf('/');
236         if (resourcePath.endsWith("/")) // skip over the trailing slash
237         {
238             indexOfSlash = resourcePath.lastIndexOf('/', indexOfSlash - 1);
239         }
240 
241         if (indexOfSlash < 0)
242         {
243             return null;
244         }
245 
246         return new String[] { resourcePath.substring(0, indexOfSlash + 1), resourcePath.substring(indexOfSlash + 1) };
247     }
248 }