1 package com.atlassian.plugin.servlet;
2
3 import com.atlassian.plugin.Plugin;
4 import com.atlassian.plugin.elements.ResourceLocation;
5 import com.atlassian.plugin.servlet.util.LastModifiedHandler;
6 import com.atlassian.plugin.util.PluginUtils;
7 import org.apache.commons.io.IOUtils;
8 import org.apache.commons.lang3.StringUtils;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.OutputStream;
17 import java.util.Date;
18
19
20
21
22
23
24
25 abstract class AbstractDownloadableResource implements DownloadableResource {
26 private static final Logger log = LoggerFactory.getLogger(AbstractDownloadableResource.class);
27
28
29
30
31
32 private static final String ATLASSIAN_WEBRESOURCE_DISABLE_MINIFICATION = "atlassian.webresource.disable.minification";
33
34
35
36 protected final Plugin plugin;
37 protected final String extraPath;
38 protected final ResourceLocation resourceLocation;
39
40
41
42 private final String location;
43 private final boolean disableMinification;
44
45 public AbstractDownloadableResource(final Plugin plugin, final ResourceLocation resourceLocation, final String extraPath) {
46 this(plugin, resourceLocation, extraPath, false);
47 }
48
49 public AbstractDownloadableResource(final Plugin plugin, final ResourceLocation resourceLocation, String extraPath, final boolean disableMinification) {
50 if ((extraPath != null) && !"".equals(extraPath.trim()) && !resourceLocation.getLocation().endsWith("/")) {
51 extraPath = "/" + extraPath;
52 }
53 this.disableMinification = disableMinification;
54 this.plugin = plugin;
55 this.extraPath = extraPath;
56 this.resourceLocation = resourceLocation;
57 this.location = resourceLocation.getLocation() + extraPath;
58 }
59
60 public void serveResource(final HttpServletRequest request, final HttpServletResponse response) throws DownloadException {
61 log.debug("Serving: {}", this);
62
63 final InputStream resourceStream = getResourceAsStreamViaMinificationStrategy();
64 if (resourceStream == null) {
65 log.warn("Resource not found: {}", this);
66 return;
67 }
68
69 final String contentType = getContentType();
70 if (StringUtils.isNotBlank(contentType)) {
71 response.setContentType(contentType);
72 }
73
74 final OutputStream out;
75 try {
76 out = response.getOutputStream();
77 } catch (final IOException e) {
78 throw new DownloadException(e);
79 }
80
81 streamResource(resourceStream, out);
82 log.debug("Serving file done.");
83 }
84
85 public void streamResource(final OutputStream out) throws DownloadException {
86 final InputStream resourceStream = getResourceAsStreamViaMinificationStrategy();
87 if (resourceStream == null) {
88 log.warn("Resource not found: {}", this);
89 return;
90 }
91
92 streamResource(resourceStream, out);
93 }
94
95
96
97
98
99
100
101
102
103
104 private void streamResource(final InputStream in, final OutputStream out) throws DownloadException {
105 try {
106 IOUtils.copy(in, out);
107 } catch (final IOException e) {
108 throw new DownloadException(e);
109 } finally {
110 IOUtils.closeQuietly(in);
111 try {
112 out.flush();
113 } catch (final IOException e) {
114 log.debug("Error flushing output stream", e);
115 }
116 }
117 }
118
119 @Override
120 public boolean isResourceModified(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
121 final Date resourceLastModifiedDate = (plugin.getDateLoaded() == null) ? new Date() : plugin.getDateLoaded();
122 final LastModifiedHandler lastModifiedHandler = new LastModifiedHandler(resourceLastModifiedDate);
123 return !lastModifiedHandler.checkRequest(httpServletRequest, httpServletResponse);
124 }
125
126 public String getContentType() {
127 return resourceLocation.getContentType();
128 }
129
130
131
132
133
134
135
136
137
138 protected abstract InputStream getResourceAsStream(String resourceLocation);
139
140
141
142
143
144
145
146 protected String getLocation() {
147 return location;
148 }
149
150 @Override
151 public String toString() {
152 final String pluginKey = plugin != null ? plugin.getKey() : "";
153 return "Resource: " + pluginKey + " " + getLocation() + " (" + getContentType() + ")";
154 }
155
156
157
158
159
160
161
162
163
164 private InputStream getResourceAsStreamViaMinificationStrategy() {
165
166 InputStream inputStream = null;
167 final String location = getLocation();
168 if (minificationStrategyInPlay(location)) {
169 final String minifiedLocation = getMinifiedLocation(location);
170 inputStream = getResourceAsStream(minifiedLocation);
171 }
172 if (inputStream == null) {
173 inputStream = getResourceAsStream(location);
174 }
175 return inputStream;
176 }
177
178
179
180
181
182
183
184
185 private boolean minificationStrategyInPlay(final String resourceLocation) {
186
187
188 if (disableMinification) {
189 return false;
190 }
191
192
193
194 try {
195 if (Boolean.getBoolean(ATLASSIAN_WEBRESOURCE_DISABLE_MINIFICATION) || PluginUtils.isAtlassianDevMode()) {
196 return false;
197 }
198 } catch (final SecurityException se) {
199
200
201 }
202
203 if (resourceLocation.endsWith(".js")) {
204
205 return !(resourceLocation.endsWith("-min.js") || resourceLocation.endsWith(".min.js"));
206 }
207 if (resourceLocation.endsWith(".css")) {
208
209 return !(resourceLocation.endsWith("-min.css") || resourceLocation.endsWith(".min.css"));
210 }
211
212
213 return false;
214 }
215
216 private String getMinifiedLocation(final String location) {
217 final int lastDot = location.lastIndexOf(".");
218
219
220 return location.substring(0, lastDot) + "-min" + location.substring(lastDot);
221 }
222 }