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.dam.core.download;
35
36 import info.magnolia.cms.filters.SelfMappingServlet;
37 import info.magnolia.cms.util.LinkUtil;
38 import info.magnolia.dam.api.Asset;
39 import info.magnolia.dam.api.AssetProvider;
40 import info.magnolia.dam.api.AssetProviderRegistry;
41 import info.magnolia.dam.api.AssetProviderRegistry.NoSuchAssetProviderException;
42 import info.magnolia.dam.api.ItemKey;
43 import info.magnolia.dam.api.PathAwareAssetProvider;
44 import info.magnolia.dam.api.PathAwareAssetProvider.PathNotFoundException;
45 import info.magnolia.dam.core.config.DamCoreConfiguration;
46
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.net.SocketException;
50 import java.util.ArrayList;
51 import java.util.Enumeration;
52 import java.util.List;
53
54 import javax.servlet.ServletException;
55 import javax.servlet.ServletOutputStream;
56 import javax.servlet.http.HttpServlet;
57 import javax.servlet.http.HttpServletRequest;
58 import javax.servlet.http.HttpServletResponse;
59
60 import org.apache.commons.io.IOUtils;
61 import org.apache.commons.lang3.StringUtils;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import com.google.inject.Inject;
66
67
68
69
70
71
72
73
74
75
76 public class DamDownloadServlet extends HttpServlet implements SelfMappingServlet {
77 private static final Logger log = LoggerFactory.getLogger(DamDownloadServlet.class);
78
79 static final String CONTENT_DISPOSITION = "Content-Disposition";
80 static final String LAST_MODIFIED = "Last-Modified";
81
82
83
84
85 public static final String FALL_BACK_PROVIDER_ID = "jcr";
86
87 private final AssetProviderRegistry assetProviderRegistry;
88 private final DamCoreConfiguration configuration;
89
90 @Inject
91 public DamDownloadServlet(final DamCoreConfiguration configuration, final AssetProviderRegistry assetProviderRegistry) {
92 this.configuration = configuration;
93 this.assetProviderRegistry = assetProviderRegistry;
94 }
95
96 @Override
97 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
98 doGet(request, response);
99 }
100
101 @Override
102 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
103 try {
104 process(request, response);
105 } catch (Exception e) {
106 log.error("error during download", e);
107 }
108 }
109
110
111
112
113
114
115
116
117 protected void process(HttpServletRequest request, HttpServletResponse response) throws Exception {
118
119 Asset asset = getAsset(request);
120
121 if (asset == null) {
122 response.sendError(HttpServletResponse.SC_NOT_FOUND);
123 return;
124 }
125 handleResourceRequest(request, response, asset);
126 }
127
128 @Override
129 public String getSelfMappingPath() {
130 return configuration.getDownloadPath() + "/*";
131 }
132
133
134
135
136
137
138 protected Asset getAsset(HttpServletRequest request) {
139 Asset asset = null;
140 String pathInfo = request.getPathInfo();
141 try {
142
143 asset = getAssetBasedOnIdentifier(pathInfo, request.getServletPath());
144
145 if (asset == null) {
146 asset = getAssetBasedOnPath(pathInfo);
147 }
148 } catch (Exception e) {
149 log.warn("Could not retrieve an asset based on the following pathInfo {}", pathInfo, e);
150 }
151 return asset;
152 }
153
154 void handleResourceRequest(HttpServletRequest req, HttpServletResponse res, Asset asset) throws Exception {
155 InputStream is = asset.getContentStream();
156 if (is == null) {
157 res.sendError(HttpServletResponse.SC_NOT_FOUND);
158 return;
159 }
160
161
162 res.setContentType(asset.getMimeType());
163
164
165 if (configuration.getContentDisposition() == null || configuration.getContentDisposition().vote(res) > 0) {
166 res.setHeader(CONTENT_DISPOSITION, "attachment; filename=\"" + asset.getFileName() + "\"");
167 }
168
169
170 if (!configuration.isEnforceDocumentMimeType()) {
171 res.setContentType(null);
172 }
173
174
175 res.setContentLength((int) asset.getFileSize());
176
177 if (asset.getLastModified() != null) {
178 res.setDateHeader(LAST_MODIFIED, asset.getLastModified().getTimeInMillis());
179 }
180
181 try {
182 sendUnCompressed(is, res);
183 } catch (IOException e) {
184 if (log.isDebugEnabled()) {
185 log.error(
186 "Download of document [" + asset.getName() + "] with headers " + getHeader(req) + " was interrupted due: " + (e.getMessage() == null ? e.getClass().getName() : e.getMessage())
187 + ":" + (e.getCause() == null ? "" : e.getCause().toString()) + ".", e);
188 } else {
189
190
191 log.info("Download of document [{}]was interrupted due: {}:{}. To see more details change logging level to debug.", asset.getName(),
192 e.getMessage() == null ? e.getClass().getName() : e.getMessage(), e.getCause() == null ? "" : e.getCause().toString());
193 }
194 } finally {
195 IOUtils.closeQuietly(is);
196 }
197 }
198
199 private List<String> getHeader(HttpServletRequest req) {
200 List<String> out = new ArrayList<String>();
201 @SuppressWarnings("unchecked")
202 Enumeration<String> names = req.getHeaderNames();
203 while (names.hasMoreElements()) {
204 String name = names.nextElement();
205 out.add(name + "=" + req.getHeader(name));
206 }
207 return out;
208 }
209
210 private void sendUnCompressed(java.io.InputStream is, HttpServletResponse res) throws IOException {
211 ServletOutputStream os = res.getOutputStream();
212 try {
213 IOUtils.copyLarge(is, os);
214 } catch (SocketException e) {
215 if (log.isDebugEnabled()) {
216 log.error(e.getMessage(), e);
217 } else {
218 log.info("Detected client abort of download. For more details increase logging level to debug.");
219 }
220 } finally {
221 IOUtils.closeQuietly(os);
222 }
223 }
224
225
226
227
228
229
230
231
232 private Asset getAssetBasedOnIdentifier(String pathInfo, String mapping) {
233
234 String keyStr = pathInfo.split("/")[1];
235
236 if (mapping.contains("static") && !ItemKey.isValid(keyStr)) {
237 keyStr = FALL_BACK_PROVIDER_ID + ":" + keyStr;
238 }
239
240 if (ItemKey.isValid(keyStr)) {
241 final ItemKey itemKey = ItemKey.from(keyStr);
242 return assetProviderRegistry.getProviderFor(itemKey).getAsset(itemKey);
243 }
244 return null;
245 }
246
247
248
249
250
251
252
253
254 private Asset getAssetBasedOnPath(String pathInfo) {
255 AssetProvider assetProvider = null;
256
257 String keyStr = pathInfo.split("/")[1];
258 String path = pathInfo;
259
260 try {
261
262 assetProvider = assetProviderRegistry.getProviderById(keyStr);
263 path = path.replaceFirst("/" + assetProvider.getIdentifier() + "/", "/");
264 } catch (NoSuchAssetProviderException nsape) {
265
266 assetProvider = assetProviderRegistry.getProviderById(FALL_BACK_PROVIDER_ID);
267 }
268
269 if (assetProvider instanceof PathAwareAssetProvider) {
270
271
272 String extension = StringUtils.substringAfterLast(path, ".");
273 String assetPath = LinkUtil.removeFingerprintAndExtensionFromLink(path);
274
275 try {
276 return ((PathAwareAssetProvider) assetProvider).getAsset(assetPath);
277 } catch (PathNotFoundException e) {
278
279 }
280
281 try {
282 return ((PathAwareAssetProvider) assetProvider).getAsset(assetPath + "." + extension);
283 } catch (PathNotFoundException e) {
284 log.warn("No asset could be found for the following path {}", assetPath);
285 }
286 }
287 log.warn("Provider {} can not support assets search based on path", assetProvider.getIdentifier());
288 return null;
289 }
290 }