KickJava   Java API By Example, From Geeks To Geeks.

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


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.IOException JavaDoc;
13 import java.util.regex.*;
14
15 import javax.servlet.ServletException JavaDoc;
16 import javax.servlet.http.*;
17
18 import org.mmbase.bridge.*;
19 import org.mmbase.util.logging.*;
20
21 /**
22  * BridgeServlet is an MMBaseServlet with a bridge Cloud in it. Extending from this makes it easy to
23  * implement servlet implemented with the MMBase bridge interfaces.
24  *
25  * An advantage of this is that security is used, which means that you cannot unintentionly serve
26  * content to the whole world which should actually be protected by the security mechanism.
27  *
28  * Another advantage is that implementation using the bridge is easier/clearer.
29  *
30  * The query of a bridge servlet can possible start with session=<session-variable-name> in which case the
31  * cloud is taken from that session attribute with that name. Otherewise 'cloud_mmbase' is
32  * supposed. All this is only done if there was a session active at all. If not, or the session
33  * variable was not found, that an anonymous cloud is used.
34  *
35  * Object can only be accessed by alias if a mapping on query string is used (so not e.g. /images/*,
36  * but /img.db). Normally this is no problem, because the alias is resolved by the image-tag. But if
37  * for some reason you need aliases to be working on the URL, you must map to URL's with a question mark.
38  *
39  * @version $Id: BridgeServlet.java,v 1.32 2006/07/14 15:30:24 michiel Exp $
40  * @author Michiel Meeuwissen
41  * @since MMBase-1.6
42  */

43 public abstract class BridgeServlet extends MMBaseServlet {
44     public static final String JavaDoc MESSAGE_ATTRIBUTE = "org.mmbase.servlet.error.message"; // javax.servlet.error.message is a bit short normally
45

46     /**
47      * Pattern used for the 'filename' part of the request. The a node-identifying string may be
48      * present in it, and it the one capturing group.
49      * It is a digit optionially followed by +.* (used in ImageServlet for url-triggered icache production)
50      */

51
52     public static final Pattern FILE_PATTERN = Pattern.compile(".*?\\D((?:session=.*?\\+)?\\d+(?:\\+.+?)?)(/.*)?");
53     // some examples captured by this regexp:
54
// /mmbase/images/session=mmbasesession+1234+s(100)/image.jpg
55
// /mmbase/images/1234+s(100)/image.jpg
56
// /mmbase/images/1234/image.jpg
57
// /mmbase/images/1234
58
// /mmbase/images?1234 (1234 not captured by regexp, but is in query!)
59

60
61     // may not be digits in servlet mapping itself!
62

63
64     private static Logger log;
65
66     /**
67      * This is constant after init.
68      */

69     private static int contextPathLength = -1;
70
71
72     private String JavaDoc lastModifiedField = null;
73
74     /**
75      * The name of the mmbase cloud which must be used. At the moment this is not supported (every
76      * mmbase cloud is called 'mmbase').
77      */

78     protected String JavaDoc getCloudName() {
79         return "mmbase";
80     }
81
82
83
84     /**
85      * Creates a QueryParts object which wraps request and response and the parse result of them.
86      * @return A QueryParts or <code>null</code> if something went wrong (in that case an error was sent, using the response).
87      */

88     protected QueryParts readQuery(HttpServletRequest req, HttpServletResponse res) throws IOException JavaDoc {
89         QueryParts qp = (QueryParts) req.getAttribute("org.mmbase.servlet.BridgeServlet$QueryParts");
90         if (qp != null) {
91             log.trace("no need parsing query");
92             if (qp.getResponse() == null && res != null) {
93                 qp.setResponse(res);
94             }
95             return qp;
96         }
97         if (log.isTraceEnabled()) {
98             log.trace("parsing query ");
99         }
100
101         String JavaDoc q = req.getQueryString();
102
103         String JavaDoc fileNamePart;
104         if (q == null || "".equals(q)) { // should be null if no query string, but http://issues.apache.org/bugzilla/show_bug.cgi?id=38113, there is version of tomcat in which it isn't.
105
// also possible to use /attachments/[session=abc+]<number>/filename.pdf
106
if (contextPathLength == -1) {
107                 contextPathLength = req.getContextPath().length();
108             }
109             String JavaDoc reqString = req.getRequestURI().substring(contextPathLength); // substring needed, otherwise there may not be digits in context path.
110

111             // some silly application-servers leave jsession id it the requestURI. Take if off again, because we'll be very confused by it.
112
if (req.isRequestedSessionIdFromURL()) {
113                 int jsessionid = reqString.indexOf(";jsessionid=");
114                 if (jsessionid != -1) {
115                     reqString = reqString.substring(0, jsessionid);
116                 }
117             }
118
119             if(log.isDebugEnabled()) {
120                 log.debug("using servlet URI " + reqString + " to find node number");
121             }
122
123             qp = readServletPath(reqString);
124             if (qp == null) {
125                 log.debug("Did not match");
126                 if(res != null) {
127                     res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Malformed URL: '" + reqString + "' does not match '" + FILE_PATTERN.pattern() + "'.");
128                     req.setAttribute(MESSAGE_ATTRIBUTE, "Malformed URL: '" + reqString + "' does not match '" + FILE_PATTERN.pattern() + "'.");
129                 } else {
130                     log.error("Malformed URL: '" + reqString + "' does not match '" + FILE_PATTERN.pattern() + "'.");
131                 }
132             } else {
133                 if (log.isDebugEnabled()) {
134                     log.debug("found " + qp);
135                 }
136             }
137         } else {
138             if(log.isDebugEnabled()) {
139                 log.debug("using query " + q + " to find node number");
140             }
141             // attachment.db?[session=abc+]number
142
qp = readQuery(q);
143             if (qp == null && res != null) {
144                 res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Malformed URL: No node number found after session.");
145                 req.setAttribute(MESSAGE_ATTRIBUTE, "Malformed URL: No node number found after session.");
146             }
147
148         }
149
150         if (qp == null) return null;
151
152         qp.setRequest(req);
153         qp.setResponse(res);
154
155         req.setAttribute("org.mmbase.servlet.BridgeServlet$QueryParts", qp);
156         return qp;
157     }
158
159
160     /**
161      *
162      * @since MMBase-1.7.4
163      */

164     public static QueryParts readServletPath(String JavaDoc servletPath) {
165         Matcher m = FILE_PATTERN.matcher(servletPath);
166         if (! m.matches()) {
167             return null;
168         }
169         QueryParts qp = readQuery(m.group(1));
170         qp.setFileName(m.group(2));
171         return qp;
172     }
173
174     /**
175      *
176      * @since MMBase-1.7.4
177      */

178     public static QueryParts readQuery(String JavaDoc query) {
179         String JavaDoc sessionName = null; // "cloud_" + getCloudName();
180
String JavaDoc nodeIdentifier;
181         if (query.startsWith("session=")) {
182             // indicated the session name in the query: session=<sessionname>+<nodenumber>
183

184             int plus = query.indexOf("+", 8);
185             if (plus == -1) {
186                 sessionName = "";
187                 nodeIdentifier = query;
188             } else {
189                 sessionName = query.substring(8, plus);
190                 nodeIdentifier = query.substring(plus + 1);
191             }
192         } else {
193             nodeIdentifier = query;
194         }
195         return new QueryParts(sessionName, nodeIdentifier);
196
197     }
198
199
200     /**
201      * Obtains a cloud object, using a QueryParts object.
202      * @return A Cloud or <code>null</code> if unsuccessful (this may not be fatal).
203      */

204     final protected Cloud getCloud(QueryParts qp) throws IOException JavaDoc {
205         log.debug("getting a cloud");
206         // trying to get a cloud from the session
207
Cloud cloud = null;
208         HttpSession session = qp.getRequest().getSession(false); // false: do not create a session, only use it
209
if (session != null) { // there is a session
210
log.debug("from session");
211             String JavaDoc sessionName = qp.getSessionName();
212             if (sessionName != null) {
213                 cloud = (Cloud) session.getAttribute(sessionName);
214             } else { // desperately searching for a cloud, perhaps someone forgot to specify 'session_name' to enforce using the session?
215
cloud = (Cloud) session.getAttribute("cloud_" + getCloudName());
216             }
217         }
218         return cloud;
219     }
220
221     /**
222      * Obtains an 'anonymous' cloud.
223      */

224     final protected Cloud getAnonymousCloud() {
225         try {
226             return ContextProvider.getDefaultCloudContext().getCloud(getCloudName());
227         } catch (org.mmbase.security.SecurityException e) {
228             log.debug("could not generate anonymous cloud");
229             // give it up
230
return null;
231         }
232     }
233
234     /**
235      * Obtains a cloud using 'class' security. If e.g. you authorize org.mmbase.servlet.ImageServlet
236      * by class-security for read all rights, it will be used.
237      * @since MMBase-1.8
238      */

239     protected Cloud getClassCloud() {
240         try {
241             return ContextProvider.getDefaultCloudContext().getCloud(getCloudName(), "class", null); // testing Class Security
242
} catch (java.lang.SecurityException JavaDoc e) {
243             log.debug("could not generate class cloud");
244             // give it up
245
return null;
246         }
247     }
248
249
250
251     /**
252      * Tries to find a Cloud which can read the given node.
253      * @since MMBase-1.8
254      */

255     protected Cloud findCloud(Cloud c, String JavaDoc nodeNumber, QueryParts query) throws IOException JavaDoc {
256
257         if (c == null || ! (c.mayRead(nodeNumber))) {
258             c = getClassCloud();
259         }
260
261         if (c == null || ! (c.mayRead(nodeNumber))) {
262             c = getCloud(query);
263         }
264         if (c == null || ! (c.mayRead(nodeNumber))) { // cannot find any cloud what-so-ever,
265
HttpServletResponse res = query.getResponse();
266             if (res != null) {
267                 res.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied to anonymous for node '" + nodeNumber + "'");
268             }
269             return null;
270         }
271         return c;
272     }
273
274     /**
275      * Servlets would often need a node. This function provides it.
276      * @param query A QueryParts object, which you must have obtained by {@link #readQuery}
277      */

278
279     final protected Node getNode(QueryParts query) throws IOException JavaDoc {
280         try {
281             if (log.isDebugEnabled()) {
282                 log.debug("query : " + query);
283             }
284
285             if (query == null) {
286                 return null;
287             } else {
288                 Node n = query.getNode();
289                 if (n != null) {
290                     return n;
291                 }
292             }
293
294             Cloud c = getAnonymousCloud(); // first try anonymously always, because then session has not to be used
295

296             String JavaDoc nodeNumber = java.net.URLDecoder.decode(query.getNodeNumber(), "UTF-8");
297
298             if (c != null && ! c.hasNode(nodeNumber)) {
299                 // ok, support for 'title' aliases too....
300
Node desperateNode = desperatelyGetNode(c, nodeNumber);
301                 if (desperateNode != null) {
302                     query.setNode(desperateNode);
303                     return desperateNode;
304                 }
305                 HttpServletResponse res = query.getResponse();
306                 if (res != null) {
307                     res.sendError(HttpServletResponse.SC_NOT_FOUND, "Node '" + nodeNumber + "' does not exist");
308                     query.getRequest().setAttribute(MESSAGE_ATTRIBUTE, "Node '" + nodeNumber + "' does not exist");
309                 }
310                 return null;
311             }
312
313             c = findCloud(c, nodeNumber, query);
314             if (c == null) {
315                 return null;
316             }
317
318             Node n = c.getNode(nodeNumber);
319             query.setNode(n);
320             return n;
321         } catch (Exception JavaDoc e) {
322             HttpServletResponse res = query.getResponse();
323             if (res != null) {
324                 query.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
325             }
326             return null;
327         }
328     }
329
330     /**
331      * Extensions can override this, to produce a node, even if cloud.hasNode failed. ('title aliases' e.g.).
332      * @since MMBase-1.7.5
333      */

334     protected Node desperatelyGetNode(Cloud cloud, String JavaDoc nodeIdentifier) {
335         return null;
336     }
337
338     /**
339      * If the node associated with the resonse is another node then the node associated with the request.\
340      * (E.g. a icache based on a url with an image node).
341      * @param qp A QueryParts object, which you must have obtained by {@link #readQuery}
342      * @param node The node which is specified on the URL (obtained by {@link #getNode}
343      * @since MMBase-1.7.4
344      */

345     protected Node getServedNode(QueryParts qp, Node node) throws IOException JavaDoc {
346         return node;
347     }
348
349     /**
350      * The idea is that a 'bridge servlet' on default serves 'nodes', and that there could be
351      * defined a 'last modified' time for nodes. This can't be determined right now, so 'now' is
352      * returned.
353      *
354      * This function is defined in HttpServlet
355      * {@inheritDoc}
356      **/

357     protected long getLastModified(HttpServletRequest req) {
358         if (lastModifiedField == null) return -1;
359         try {
360             QueryParts query = readQuery(req, null);
361             Node node = getServedNode(query, getNode(query));
362             if (node != null) { // && node.getNodeManager().hasField(lastModifiedField)) {
363
return node.getDateValue(lastModifiedField).getTime();
364             } else {
365                 return -1;
366             }
367         } catch (IOException JavaDoc ieo) {
368             return -1;
369         }
370     }
371
372     /**
373      * Inits lastmodifiedField.
374      * {@inheritDoc}
375      */

376
377     public void init() throws ServletException JavaDoc {
378         super.init();
379         lastModifiedField = getInitParameter("lastmodifiedfield");
380         if ("".equals(lastModifiedField)) lastModifiedField = null;
381         log = Logging.getLoggerInstance(BridgeServlet.class);
382         if (lastModifiedField != null) {
383             log.service("Field '" + lastModifiedField + "' will be used to calculate lastModified");
384         }
385     }
386
387     /**
388      * Keeps track of determined information, to avoid redetermining it.
389      */

390     final static public class QueryParts {
391         private String JavaDoc sessionName;
392         private String JavaDoc nodeIdentifier;
393         private HttpServletRequest req;
394         private HttpServletResponse res;
395         private Node node;
396         private Node servedNode;
397         private String JavaDoc fileName;
398         QueryParts(String JavaDoc sessionName, String JavaDoc nodeIdentifier) {
399             this.sessionName = sessionName;
400             this.nodeIdentifier = nodeIdentifier;
401
402         }
403         void setNode(Node node) {
404             this.node = node;
405         }
406         Node getNode() {
407             return node;
408         }
409         void setServedNode(Node node) {
410             this.servedNode = node;
411         }
412         Node getServedNode() {
413             return servedNode;
414         }
415         void setFileName(String JavaDoc fn) {
416             fileName = fn;
417         }
418         public String JavaDoc getFileName() {
419             return fileName;
420         }
421         public String JavaDoc getSessionName() {
422             return sessionName;
423         }
424         public String JavaDoc getNodeNumber() {
425             int i = nodeIdentifier.indexOf('+');
426             if (i > 0) {
427                 return nodeIdentifier.substring(0, i);
428             } else {
429                 return nodeIdentifier;
430             }
431         }
432         void setRequest(HttpServletRequest req) {
433             this.req = req;
434         }
435         void setResponse(HttpServletResponse res) {
436             this.res = res;
437         }
438
439         HttpServletRequest getRequest() {
440             return req;
441         }
442         HttpServletResponse getResponse() {
443             return res;
444         }
445
446         /**
447          * @since MMBase-1.7.4
448          */

449         public String JavaDoc getNodeIdentifier() {
450             return nodeIdentifier;
451         }
452
453         public String JavaDoc toString() {
454             return sessionName == null ? nodeIdentifier : "session=" + sessionName + "+" + nodeIdentifier;
455         }
456
457
458     }
459
460     /**
461      * Just to test to damn regexp
462      */

463     public static void main(String JavaDoc[] argv) {
464
465         Matcher m = FILE_PATTERN.matcher(argv[0]);
466         if (! m.matches()) {
467             System.out.println("Didn't match");
468         } else {
469             System.out.println("Found node " + m.group(1));
470         }
471     }
472
473 }
474
Popular Tags