1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.cms.servlets;
35
36 import info.magnolia.cms.beans.config.MIMEMapping;
37 import info.magnolia.cms.util.ClasspathResourcesUtil;
38
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.net.URL;
42 import java.net.URLConnection;
43 import java.util.Hashtable;
44 import java.util.Map;
45
46 import javax.servlet.ServletException;
47 import javax.servlet.ServletOutputStream;
48 import javax.servlet.http.HttpServlet;
49 import javax.servlet.http.HttpServletRequest;
50 import javax.servlet.http.HttpServletResponse;
51
52 import org.apache.commons.io.IOUtils;
53 import org.apache.commons.lang3.StringUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57
58
59
60
61
62
63
64
65
66 @Deprecated
67 public class ClasspathSpool extends HttpServlet {
68
69
70
71
72 public static final String MGNL_DEFAULT_RESOURCES_ROOT = "/mgnl-resources";
73
74 private final static Logger log = LoggerFactory.getLogger(ClasspathSpool.class);
75
76 private String resourcesRoot;
77
78 @Override
79 protected long getLastModified(HttpServletRequest req) {
80 String filePath = this.getFilePath(req);
81 try {
82 URL url = ClasspathResourcesUtil.getResource(resourcesRoot + filePath);
83 if (url != null) {
84 URLConnection connection = url.openConnection();
85
86 connection.setDoInput(false);
87 connection.setDoOutput(false);
88
89 long lastModified = connection.getLastModified();
90 InputStream is = null;
91 try {
92 is = connection.getInputStream();
93 } finally {
94 IOUtils.closeQuietly(is);
95 }
96 return lastModified;
97 }
98 } catch (IOException e) {
99
100 }
101
102 return -1;
103 }
104
105
106
107
108
109
110 @Override
111 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
112
113 String filePath = getFilePath(request);
114
115 log.warn("ClasspathSpool servlet is used for {}. The servlet is deprecated and will be removed in a future release. Please use info.magnolia.module.resources.servlets.ResourcesServlet instead.", filePath);
116
117 if (StringUtils.contains(filePath, "*")) {
118 streamMultipleFile(response, filePath);
119 } else if (StringUtils.contains(filePath, "|")) {
120 String[] paths = StringUtils.split(filePath, "|");
121 streamMultipleFile(response, paths);
122 } else {
123 streamSingleFile(response, filePath);
124 }
125 }
126
127 protected String getFilePath(HttpServletRequest request) {
128
129 String filePath = (String) request.getAttribute("javax.servlet.include.path_info");
130
131
132 if (StringUtils.isEmpty(filePath)) {
133 filePath = (String) request.getAttribute("javax.servlet.forward.path_info");
134 }
135
136
137 if (StringUtils.isEmpty(filePath)) {
138 filePath = request.getPathInfo();
139 }
140 return filePath;
141 }
142
143 private Map<String, String[]> multipleFilePathsCache;
144
145
146
147
148 @Override
149 public void init() throws ServletException {
150 super.init();
151 multipleFilePathsCache = new Hashtable<String, String[]>();
152 resourcesRoot = StringUtils.defaultIfEmpty(getInitParameter("resourcesRoot"), MGNL_DEFAULT_RESOURCES_ROOT);
153
154 URL url = ClasspathResourcesUtil.getResource(resourcesRoot);
155 log.debug("resources root is {}", resourcesRoot);
156 if (url == null) {
157 log.warn("Resource classpath root {} does not seem to exist. Some resources might not be available, please check your configuration. Falling back to default resources root {}", resourcesRoot, MGNL_DEFAULT_RESOURCES_ROOT);
158
159 resourcesRoot = MGNL_DEFAULT_RESOURCES_ROOT;
160 }
161 }
162
163
164
165
166 @Override
167 public void destroy() {
168 super.destroy();
169 multipleFilePathsCache.clear();
170 }
171
172
173
174
175
176 private void streamMultipleFile(HttpServletResponse response, String filePath) throws IOException {
177 log.debug("aggregating files for request {}", filePath);
178
179 String[] paths = multipleFilePathsCache.get(filePath);
180 if (paths == null) {
181 final String startsWith = resourcesRoot + StringUtils.substringBefore(filePath, "*");
182 final String endsWith = StringUtils.substringAfterLast(filePath, "*");
183
184 paths = ClasspathResourcesUtil.findResources(new ClasspathResourcesUtil.Filter() {
185
186 @Override
187 public boolean accept(String name) {
188 return name.startsWith(startsWith) && name.endsWith(endsWith);
189 }
190 });
191 }
192 multipleFilePathsCache.put(filePath, paths);
193
194 if (paths.length == 0) {
195 response.sendError(HttpServletResponse.SC_NOT_FOUND);
196 return;
197 }
198
199 streamMultipleFile(response, paths);
200 }
201
202 private void streamMultipleFile(HttpServletResponse response, String[] paths) throws IOException {
203 ServletOutputStream out = response.getOutputStream();
204 try {
205 InputStream in = null;
206
207 for (String path : paths) {
208 try {
209 if (!path.startsWith(resourcesRoot)) {
210 path = resourcesRoot + path;
211 }
212 in = ClasspathResourcesUtil.getStream(path);
213 if (in != null) {
214 IOUtils.copy(in, out);
215 }
216 } finally {
217 IOUtils.closeQuietly(in);
218 }
219 }
220 if (paths != null && paths.length != 0) {
221 response.setContentType(resolveContentType(paths));
222 }
223 } finally {
224 out.flush();
225 IOUtils.closeQuietly(out);
226 }
227 }
228
229 private void streamSingleFile(HttpServletResponse response, String filePath) throws IOException {
230 InputStream in = null;
231
232
233 String path = resourcesRoot + filePath;
234 try {
235 in = ClasspathResourcesUtil.getStream(path);
236 } catch (IOException e) {
237 IOUtils.closeQuietly(in);
238 }
239
240 if (in == null) {
241 if (!response.isCommitted()) {
242 response.sendError(HttpServletResponse.SC_NOT_FOUND);
243 }
244 return;
245 }
246
247 ServletOutputStream out = null;
248 try {
249 response.setContentType(resolveContentType(path));
250 out = response.getOutputStream();
251 IOUtils.copy(in, out);
252 out.flush();
253 } catch (IOException e) {
254
255
256 log.debug("Unable to spool resource due to a {} exception", e.getClass().getName());
257 if (!response.isCommitted()) {
258 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
259 }
260 } finally {
261 IOUtils.closeQuietly(out);
262 IOUtils.closeQuietly(in);
263 }
264 }
265
266 private String resolveContentType(String... paths) {
267 String extension = null;
268 boolean sameExtensionForAllResources = true;
269
270 for (String path: paths) {
271 String actualExtension = resolveExtension(path);
272 if (extension == null) {
273 extension = actualExtension;
274 } else if (!StringUtils.equalsIgnoreCase(extension, actualExtension)) {
275 sameExtensionForAllResources = false;
276 break;
277 }
278 }
279 if (sameExtensionForAllResources) {
280 return MIMEMapping.getMIMETypeOrDefault(extension);
281 }
282 return MIMEMapping.getMIMETypeOrDefault(StringUtils.EMPTY);
283 }
284
285 private String resolveExtension(String path) {
286 return StringUtils.substringAfterLast(StringUtils.substringAfterLast(path, "/"), ".");
287 }
288
289 }