PageRenderTime 41ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/atlassian-plugins-webresource-common/src/main/java/com/atlassian/plugin/servlet/AbstractDownloadableResource.java

https://bitbucket.org/purewind/atlassian-plugins
Java | 229 lines | 130 code | 27 blank | 72 comment | 21 complexity | bb7015f001e19ee39ff90b5304f5a29d MD5 | raw file
  1. package com.atlassian.plugin.servlet;
  2. import com.atlassian.plugin.Plugin;
  3. import com.atlassian.plugin.elements.ResourceLocation;
  4. import com.atlassian.plugin.servlet.util.LastModifiedHandler;
  5. import com.atlassian.plugin.util.PluginUtils;
  6. import org.apache.commons.io.IOUtils;
  7. import org.apache.commons.lang.StringUtils;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.io.OutputStream;
  15. import java.util.Date;
  16. /**
  17. * This base class is used to provide the ability to server minified versions of
  18. * files if required and available.
  19. *
  20. * @since 2.2
  21. */
  22. abstract class AbstractDownloadableResource implements DownloadableResource {
  23. private static final Logger log = LoggerFactory.getLogger(AbstractDownloadableResource.class);
  24. /**
  25. * This is a the system environment variable to set to disable the
  26. * minification naming strategy used to find web resources.
  27. */
  28. private static final String ATLASSIAN_WEBRESOURCE_DISABLE_MINIFICATION = "atlassian.webresource.disable.minification";
  29. /* the following protected fields are marked final since 2.5 */
  30. protected final Plugin plugin;
  31. protected final String extraPath;
  32. protected final ResourceLocation resourceLocation;
  33. // PLUG-538 cache this so we don't recreate the string every time it is
  34. // called
  35. private final String location;
  36. private final boolean disableMinification;
  37. public AbstractDownloadableResource(final Plugin plugin, final ResourceLocation resourceLocation, final String extraPath) {
  38. this(plugin, resourceLocation, extraPath, false);
  39. }
  40. public AbstractDownloadableResource(final Plugin plugin, final ResourceLocation resourceLocation, String extraPath, final boolean disableMinification) {
  41. if ((extraPath != null) && !"".equals(extraPath.trim()) && !resourceLocation.getLocation().endsWith("/")) {
  42. extraPath = "/" + extraPath;
  43. }
  44. this.disableMinification = disableMinification;
  45. this.plugin = plugin;
  46. this.extraPath = extraPath;
  47. this.resourceLocation = resourceLocation;
  48. this.location = resourceLocation.getLocation() + extraPath;
  49. }
  50. public void serveResource(final HttpServletRequest request, final HttpServletResponse response) throws DownloadException {
  51. log.debug("Serving: {}", this);
  52. final InputStream resourceStream = getResourceAsStreamViaMinificationStrategy();
  53. if (resourceStream == null) {
  54. log.warn("Resource not found: {}", this);
  55. return;
  56. }
  57. final String contentType = getContentType();
  58. if (StringUtils.isNotBlank(contentType)) {
  59. response.setContentType(contentType);
  60. }
  61. final OutputStream out;
  62. try {
  63. out = response.getOutputStream();
  64. } catch (final IOException e) {
  65. throw new DownloadException(e);
  66. }
  67. streamResource(resourceStream, out);
  68. log.debug("Serving file done.");
  69. }
  70. public void streamResource(final OutputStream out) throws DownloadException {
  71. final InputStream resourceStream = getResourceAsStreamViaMinificationStrategy();
  72. if (resourceStream == null) {
  73. log.warn("Resource not found: {}", this);
  74. return;
  75. }
  76. streamResource(resourceStream, out);
  77. }
  78. /**
  79. * Copy from the supplied OutputStream to the supplied InputStream. Note
  80. * that the InputStream will be closed on completion.
  81. *
  82. * @param in the stream to read from
  83. * @param out the stream to write to
  84. * @throws DownloadException if an IOException is encountered writing to the
  85. * out stream
  86. */
  87. private void streamResource(final InputStream in, final OutputStream out) throws DownloadException {
  88. try {
  89. IOUtils.copy(in, out);
  90. } catch (final IOException e) {
  91. throw new DownloadException(e);
  92. } finally {
  93. IOUtils.closeQuietly(in);
  94. try {
  95. out.flush();
  96. } catch (final IOException e) {
  97. log.debug("Error flushing output stream", e);
  98. }
  99. }
  100. }
  101. /**
  102. * Checks any "If-Modified-Since" header from the request against the
  103. * plugin's loading time, since plugins can't be modified after they've been
  104. * loaded this is a good way to determine if a plugin resource has been
  105. * modified or not. If this method returns true, don't do any more
  106. * processing on the request -- the response code has already been set to
  107. * "304 Not Modified" for you, and you don't need to serve the file.
  108. */
  109. public boolean isResourceModified(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
  110. final Date resourceLastModifiedDate = (plugin.getDateLoaded() == null) ? new Date() : plugin.getDateLoaded();
  111. final LastModifiedHandler lastModifiedHandler = new LastModifiedHandler(resourceLastModifiedDate);
  112. return !lastModifiedHandler.checkRequest(httpServletRequest, httpServletResponse);
  113. }
  114. public String getContentType() {
  115. return resourceLocation.getContentType();
  116. }
  117. /**
  118. * Returns an {@link InputStream} to stream the resource from based on
  119. * resource name.
  120. *
  121. * @param resourceLocation the location of the resource to try and load
  122. * @return an InputStream if the resource can be found or null if cant be
  123. * found
  124. */
  125. protected abstract InputStream getResourceAsStream(String resourceLocation);
  126. /**
  127. * This is called to return the location of the resource that this object
  128. * represents.
  129. *
  130. * @return the location of the resource that this object represents.
  131. */
  132. protected String getLocation() {
  133. return location;
  134. }
  135. @Override
  136. public String toString() {
  137. final String pluginKey = plugin != null ? plugin.getKey() : "";
  138. return "Resource: " + pluginKey + " " + getLocation() + " (" + getContentType() + ")";
  139. }
  140. /**
  141. * This is called to use a minification naming strategy to find resources.
  142. * If a minified file cant by found then the base location is used as the
  143. * fall back
  144. *
  145. * @return an InputStream r null if nothing can be found for the resource
  146. * name
  147. */
  148. private InputStream getResourceAsStreamViaMinificationStrategy() {
  149. InputStream inputStream = null;
  150. final String location = getLocation();
  151. if (minificationStrategyInPlay(location)) {
  152. final String minifiedLocation = getMinifiedLocation(location);
  153. inputStream = getResourceAsStream(minifiedLocation);
  154. }
  155. if (inputStream == null) {
  156. inputStream = getResourceAsStream(location);
  157. }
  158. return inputStream;
  159. }
  160. /**
  161. * Returns true if the minification strategy should be applied to a given
  162. * resource name
  163. *
  164. * @param resourceLocation the location of the resource
  165. * @return true if the minification strategy should be used.
  166. */
  167. private boolean minificationStrategyInPlay(final String resourceLocation) {
  168. // check if minification has been turned off for this resource (at the
  169. // module level)
  170. if (disableMinification) {
  171. return false;
  172. }
  173. // secondly CHECK if we have a System property set to true that DISABLES
  174. // the minification
  175. try {
  176. if (Boolean.getBoolean(ATLASSIAN_WEBRESOURCE_DISABLE_MINIFICATION) || PluginUtils.isAtlassianDevMode()) {
  177. return false;
  178. }
  179. } catch (final SecurityException se) {
  180. // some app servers might have protected access to system
  181. // properties. Unlikely but lets be defensive
  182. }
  183. // We only minify .js or .css files
  184. if (resourceLocation.endsWith(".js")) {
  185. // Check if it is already the minified version of the file
  186. return !(resourceLocation.endsWith("-min.js") || resourceLocation.endsWith(".min.js"));
  187. }
  188. if (resourceLocation.endsWith(".css")) {
  189. // Check if it is already the minified version of the file
  190. return !(resourceLocation.endsWith("-min.css") || resourceLocation.endsWith(".min.css"));
  191. }
  192. // Not .js or .css, don't bother trying to find a minified version (may
  193. // save some file operations)
  194. return false;
  195. }
  196. private String getMinifiedLocation(final String location) {
  197. final int lastDot = location.lastIndexOf(".");
  198. // this can never but -1 since the method call is protected by a call to
  199. // minificationStrategyInPlay() first
  200. return location.substring(0, lastDot) + "-min" + location.substring(lastDot);
  201. }
  202. }