KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > servlet > HandleServlet


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.servlet;
11
12 import java.io.*;
13 import java.util.Map JavaDoc;
14 import java.util.regex.Pattern JavaDoc;
15
16 import javax.servlet.ServletException JavaDoc;
17 import javax.servlet.http.*;
18
19 import org.mmbase.bridge.*;
20
21 import org.mmbase.util.*;
22 import org.mmbase.util.logging.*;
23
24
25 /**
26  * Base servlet for nodes with a 'handle' field. It serves as a basic implementation for more
27  * specialized servlets. The mime-type is always application/x-binary, forcing the browser to
28  * download.
29  *
30  * @version $Id: HandleServlet.java,v 1.29 2006/07/18 12:36:56 michiel Exp $
31  * @author Michiel Meeuwissen
32  * @since MMBase-1.6
33  * @see ImageServlet
34  * @see AttachmentServlet
35  */

36 public class HandleServlet extends BridgeServlet {
37     private static Logger log;
38
39     private long expires; // expires so many milliseconds after serving
40

41     protected Map JavaDoc getAssociations() {
42         Map JavaDoc a = super.getAssociations();
43         // Can do the following:
44
a.put("attachments", new Integer JavaDoc(0));
45         a.put("downloads", new Integer JavaDoc(20)); // good at this (because it does not determine the mime-type)
46
a.put("images", new Integer JavaDoc(-10)); // bad in images (no mime-type, no awareness of icaches)
47
return a;
48     }
49
50     /**
51      * Takes care of the 'expire' init-parameter.
52      * {@inheritDoc}
53      */

54     public void init() throws ServletException JavaDoc {
55         super.init();
56         log = Logging.getLoggerInstance(HandleServlet.class);
57
58         String JavaDoc expiresParameter = getInitParameter("expire");
59         if (expiresParameter == null) {
60             // default: one hour
61
expires = 60 * 60 * 1000;
62         } else {
63             expires = new Integer JavaDoc(expiresParameter).intValue() * 1000;
64         }
65     }
66
67     // just to get HandleServlet in the stacktrace.
68
protected Cloud getClassCloud() {
69         return super.getClassCloud();
70     }
71
72     /**
73      * Forces download in browsers.
74      * This is overriden in several extensions.
75      */

76     protected String JavaDoc getMimeType(Node node) {
77         return "application/x-binary";
78     }
79
80
81     protected static final Pattern JavaDoc legalizeFileName = Pattern.compile("[\\/\\:\\;\\\\\"]+");
82
83     /**
84      * @since MMBase-1.8
85      */

86     protected String JavaDoc getFileName(final Node node, Node titleNode, final String JavaDoc def) {
87         if (titleNode == null) titleNode = node;
88         NodeManager nm = titleNode.getNodeManager();
89         // Try to find a sensible filename to use in the content-disposition header.
90
String JavaDoc fileName;
91         if (node == titleNode) {
92             fileName = nm.hasField("filename") ? titleNode.getStringValue("filename") : null;
93         } else {
94             if (nm.hasField("filename")) {
95                 fileName = titleNode.getStringValue("filename");
96                 String JavaDoc ext = node.getFunctionValue("format", null).toString();
97                 if (! ext.equals(titleNode.getFunctionValue("format", null).toString())) {
98                     fileName += '.' + ext;
99                 }
100             } else {
101                 fileName = null;
102             }
103         }
104         if (fileName != null) {
105             int backSlash = fileName.lastIndexOf("\\");
106             // if uploaded in MSIE, then the path may be in the fileName
107
// this is also fixed in the set-processor, but if that is or was missing, be gracefull here.
108
if (backSlash > -1) {
109                 fileName = fileName.substring(backSlash + 1);
110             }
111         }
112
113         if (fileName == null || fileName.equals("")) {
114             fileName = nm.hasField("title") ? titleNode.getStringValue("title") + '.' + node.getFunctionValue("format", null).toString() : null;
115         }
116         if (fileName == null || fileName.equals("")) {
117             fileName = nm.hasField("name") ? titleNode.getStringValue("name") + '.' + node.getFunctionValue("format", null).toString() : null;
118         }
119         if (fileName == null || fileName.equals("")) { // give it up
120
fileName = def + "." + node.getFunctionValue("format", null).toString();
121         }
122
123         return legalizeFileName.matcher(fileName).replaceAll("_");
124     }
125
126     /**
127      * Sets the content disposition header.
128      * @return true on success
129      */

130     protected boolean setContent(QueryParts query, Node node, String JavaDoc mimeType) throws IOException {
131         String JavaDoc disposition;
132         String JavaDoc fileNamePart = query.getFileName();
133         if(fileNamePart != null && fileNamePart.startsWith("/inline/")) {
134             disposition = "inline";
135         } else {
136             disposition = "attachment";
137         }
138         query.getResponse().setHeader("Content-Disposition", disposition + "; filename=\"" + getFileName(node, null, "mmbase-attachment")+ "\"");
139         //res.setHeader("X-MMBase-1", "Not sending Content-Disposition because this might confuse Microsoft Internet Explorer");
140
return true;
141     }
142
143     /**
144      * Sets the exires header.
145      * @return true on sucess
146      */

147     protected boolean setExpires(HttpServletResponse res, Node node) {
148         if (node.getNodeManager().getName().equals("icaches")) {
149             // cached images never expire, they cannot change without receiving a new number, thus changing the URL.
150
long never = System.currentTimeMillis() + (long) (365.25 * 24 * 60 * 60 * 1000);
151             // one year in future, this is considered to be sufficiently 'never'.
152
res.setDateHeader("Expires", never);
153         } else {
154             long later = System.currentTimeMillis() + expires;
155             res.setDateHeader("Expires", later);
156         }
157         return true;
158     }
159
160     /**
161      * Sets cache-controlling headers. Only nodes which are to be served to 'anonymous' might be
162      * (front proxy) cached. To other nodes there might be read restrictions, so they should not be
163      * stored in front-proxy caches.
164      *
165      * @return true if cacheing is disabled.
166      * @since MMBase-1.7
167      */

168     protected boolean setCacheControl(HttpServletResponse res, Node node) {
169         if (!node.getCloud().getUser().getRank().equals(org.mmbase.security.Rank.ANONYMOUS)) {
170             res.setHeader("Cache-Control", "private");
171             // res.setHeader("Pragma", "no-cache"); // for http 1.0 : is frustrating IE when https
172
// res.setHeader("Pragma", "no-store"); // no-cache not working in apache!
173
// we really don't want this to remain in proxy caches, but the http 1.0 way is making IE not work.
174
return true;
175         } else {
176             res.setHeader("Cache-Control", "public");
177             return false;
178         }
179     }
180
181
182     /**
183      * Serves a node with a byte[] handle field as an attachment.
184      */

185
186     public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException JavaDoc, IOException {
187         QueryParts query = readQuery(req, res);
188         Node queryNode = getNode(query);
189         if (queryNode == null) {
190             return;
191         }
192
193         //res.setHeader("X-MMBase-Version", org.mmbase.Version.get());
194
Node node = getServedNode(query, getNode(query));
195
196         if (node == null) {
197             return;
198         }
199
200         NodeManager manager = node.getNodeManager();
201         if (! manager.hasField("handle")) {
202             res.sendError(HttpServletResponse.SC_NOT_FOUND, "No handle found in node " + node.getNumber());
203             req.setAttribute(MESSAGE_ATTRIBUTE, "No handle found in node " + node.getNumber());
204             return;
205         }
206
207         // fill the headers
208
res.setDateHeader("Date", System.currentTimeMillis());
209
210         String JavaDoc mimeType = getMimeType(node);
211         res.setContentType(mimeType);
212
213         if (node.isNull("handle")) {
214             return;
215         }
216         InputStream bytes = node.getInputStreamValue("handle");
217
218
219         //remove additional information left by PhotoShop 7 in jpegs
220
//this information may crash Internet Exploder. that's why you need to remove it.
221
//With PS 7, Adobe decided by default to embed XML-encoded "preview" data into JPEG files,
222
//using a feature of the JPEG format that permits embedding of arbitrarily-named "profiles".
223
//In theory, these files are valid according to the JPEG specifications.
224
//However they break many applications, including Quark and, significantly,
225
//various versions of Internet Explorer on various platforms.
226

227         boolean canSendLength = true;
228         if (mimeType.equals("image/jpeg") || mimeType.equals("image/jpg")) {
229             bytes = new IECompatibleJpegInputStream(bytes);
230             canSendLength = false;
231             //res.setHeader("X-MMBase-IECompatibleJpeg", "This image was filtered, because Microsoft Internet Explorer might crash otherwise");
232
}
233
234         if (!setContent(query, node, mimeType)) {
235             return;
236         }
237         setExpires(res, node);
238         setCacheControl(res, node);
239
240         if (canSendLength) {
241             int size = -1;
242             if (manager.hasField("size")) {
243                 size = node.getIntValue("size");
244             } else if (manager.hasField("filesize")) {
245                 size = node.getIntValue("filesize");
246             }
247             if (size >= 0) {
248                 res.setContentLength(size);
249             }
250             log.debug("Serving node " + node.getNumber() + " with bytes " + size);
251         } else {
252             log.debug("Serving node " + node.getNumber() + " with unknown size, because IE sucks");
253         }
254         sendBytes(res, bytes);
255     }
256
257
258     /**
259      * Utility function to send bytes at the end of doGet implementation.
260      * @deprecated
261      */

262     final protected void sendBytes(HttpServletResponse res, byte[] bytes) throws IOException {
263         int fileSize = bytes.length;
264         res.setContentLength(fileSize);
265
266         BufferedOutputStream out = new BufferedOutputStream(res.getOutputStream());
267         out.write(bytes, 0, fileSize);
268         out.flush();
269     }
270     final protected void sendBytes(HttpServletResponse res, InputStream bytes) throws IOException {
271         if (log.isDebugEnabled()) {
272             log.debug("Sending by " + bytes.getClass());
273         }
274         BufferedOutputStream out = new BufferedOutputStream(res.getOutputStream());
275         byte[] buf = new byte[1024];
276         int b = 0;
277         while ((b = bytes.read(buf)) != -1) {
278             out.write(buf, 0, b);
279         }
280         out.flush();
281         bytes.close();
282         out.close();
283     }
284
285     public static void main(String JavaDoc argv[]) {
286         System.out.println(legalizeFileName.matcher(argv[0]).replaceAll("_"));
287     }
288
289 }
290
Popular Tags