In the optimalization project meeting of today we have decided that
JamesServlet and extensions can be moved to the 'scan application' (if at
least the vote succeeds).
The most noticeable extension of JamesServlet is servdb, which can (among
others) serve the bytes of image nodes and attachments.
An alternative for this functionality are ImageServlet and AttachentServlet
(extensions of MMBaseServlet).
Currently there are a few fundamental differences between servdb and those
MMBaseServlet extensions:
1. ImageServlet does not accept transformation templates on the URL.
2. These servlet use bridge, and therefore security. Especially to check for
read-rights. Servdb does not.
The first issue could be considered a good thing, but since servdb could do
it, and that one will be dropped (moved to scan which is nearly the same..),
I implemented an alternative in ImageServlet. This behavior is configurable
though, by an init-parameter of the Servlet in web.xml
<init-param>
<param-name>convert</param-name>
<param-value>true</param-value>
</init-param>
This 'convert' parameter defaults to false, when not set at all. I'll add it
to the default web.xml (also set to false), do document it.
The second issue is also a good thing, but sometimes you may want to bypass
security for such servlets. E.g. because it would be cumbersome to configure
security right.
As suggested in the thread "Images and users/permissions" of sept 28, it may
be a nice idea if such servlets could profit from the 1.8 feature 'class
security'.
So I implemented this, because this can also practicly take away this second
difference between servdb and ImagesServlet/AttachmentServlet.
If you add
<authenticate class="org\.mmbase\.servlet\.ImageServlet">
<property name="username" value="admin" />
</authenticate>
to classauthentication.xml then ImageServlet will receive admin rights if a
it tries to serve in image which is not readable by anonymous.
While being at it, I added a last improvement to these servlets. This
improvement is actually unrelated to servdb. In 1.8 you can easily define
'lastmodified' field, which will automaticly be filled with the time of last
modification (see fieldtypedefinitions.xml). Since these servlets do basicly
serve one node, it would be nice if they could use this field, to fill in
the LastModified HTTP header of the response.
In the offered implementation this can be trigged with the init-parameter:
<init-param>
<param-name>lastmodifiedfield</param-name>
<param-value>lastmodified</param-value>
</init-param>
(on ImageServlet and AttachmentServlet).
The configured field (if set, and not set to empty string) will be used to
determin the last-modified header. Sadly - if you are going to check it out
- it does not work yet for icaches, but that is because the lastmodified
field is not filled in those (an issue for the field-types project).
To reach the above goals, I added a few (protected) methods to
BridgeServlet, HandleServlet, ImageServlet and AttachmentServlet. Also I
changed the prototypes of a few methods:
final protected Cloud getCloud(HttpServletRequest req, HttpServletResponse res,
QueryParts qp) throws IOException {
becomes:
final protected Cloud getCloud(QueryParts qp) throws IOException {
final protected Node getNode(HttpServletRequest req, HttpServletResponse res) throws
IOException {
becomes:
final protected Node getNode(QueryParts query) throws IOException {
and:
protected boolean setContent(HttpServletRequest req, HttpServletResponse res, Node
node, String mimeType) throws IOException {
becomes
protected boolean setContent(QueryParts query, Node node, String mimeType) throws
IOException {
This simplified implementations, and makes it possible to avoid all overhead
(e.g. if the icache node is alreayd determined in the setLastModified
method, you don't want to do that again in doGet itself). These changes are
not actually backwards compatible with existing 3rd party extensions of one
of these servlets, but those would be easy to fix. I don't know if such
extensions exist, and if so, if they indeed call these methods (most were
final, so could not be overridden). If this is important to someone
deprecated old-style methods could be added.
I offer the described improvements as a HACK to the 1.8 branch of MMBase.
The involved classes are attached.
START OF VOTING: 2004-10-05 22:00
END OF CALL: 2004-10-08 22:00
[_] +1 (YES)
[_] +0 (ABSTAIN )
[_] -1 (NO), because :
[_] VETO, because:
--
Michiel Meeuwissen mihxil'
Mediacentrum 140 H'sum [] ()
+31 (0)35 6772979 nl_NL eo_XX en_US
/*
This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.
The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license
*/
package org.mmbase.servlet;
import java.io.*;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.mmbase.bridge.*;
import org.mmbase.util.*;
import org.mmbase.util.logging.*;
/**
* Base servlet for nodes with a 'handle' field. It serves as a basic implementation for more
* specialized servlets. The mime-type is always application/x-binary, forcing the browser to
* download.
*
* @version $Id: HandleServlet.java,v 1.16 2004/09/30 14:54:56 pierre Exp $
* @author Michiel Meeuwissen
* @since MMBase-1.6
* @see ImageServlet
* @see AttachmentServlet
*/
public class HandleServlet extends BridgeServlet {
private static Logger log;
private long expires; // expires so many milliseconds after serving
protected Map getAssociations() {
Map a = super.getAssociations();
// Can do the following:
a.put("attachments", new Integer(0));
a.put("downloads", new Integer(20)); // good at this (because it does not determine the mime-type)
a.put("images", new Integer(-10)); // bad in images (no mime-type, no awareness of icaches)
return a;
}
/**
* Takes care of the 'expire' init-parameter.
* [EMAIL PROTECTED]
*/
public void init() throws ServletException {
super.init();
log = Logging.getLoggerInstance(HandleServlet.class);
String expiresParameter = getInitParameter("expire");
if (expiresParameter == null) {
// default: one hour
expires = 60 * 60 * 1000;
} else {
expires = new Integer(expiresParameter).intValue() * 1000;
}
}
// just to get HandleServlet in the stacktrace.
protected Cloud getClassCloud() {
return super.getClassCloud();
}
/**
* Forces download in browsers.
* This is overriden in several extensions.
*/
protected String getMimeType(Node node) {
return "application/x-binary";
}
/**
* Sets the content disposition header.
* @return true on success
*/
protected boolean setContent(QueryParts query, Node node, String mimeType) throws IOException {
// Try to find a sensible filename to use in the content-disposition header.
String fileName = node.getStringValue("filename");
if (fileName == null || fileName.equals("")) {
fileName = node.getStringValue("title");
if (fileName == null || fileName.equals("")) { // give it up
fileName = "mmbase-attachment";
}
// try to add an extension.
String format = node.getFunctionValue("format", null).toString();
if (format != null && !format.equals("")) {
fileName += "." + format;
}
}
StringObject fn = new StringObject(fileName);
fn.replace(" ", "_");
// Why we don't set Content-Disposition:
// - IE can't handle that. (IE sucks!)
query.getResponse().setHeader("Content-Disposition", "attachment; filename=\"" + fn + "\"");
//res.setHeader("X-MMBase-1", "Not sending Content-Disposition because this might confuse Microsoft Internet Explorer");
return true;
}
/**
* Sets the exires header.
* @return true on sucess
*/
protected boolean setExpires(HttpServletResponse res, Node node) {
if (node.getNodeManager().getName().equals("icaches")) {
// cached images never expire, they cannot change without receiving a new number, thus changing the URL.
long never = System.currentTimeMillis() + (long) (365.25 * 24 * 60 * 60 * 1000);
// one year in future, this is considered to be sufficiently 'never'.
res.setDateHeader("Expires", never);
} else {
long later = System.currentTimeMillis() + expires;
res.setDateHeader("Expires", later);
}
return true;
}
/**
* Sets cache-controlling headers. Only nodes which are to be served to 'anonymous' might be
* (front proxy) cached. To other nodes there might be read restrictions, so they should not be
* stored in front-proxy caches.
*
* @return true if cacheing is disabled.
* @since MMBase-1.7
*/
protected boolean setCacheControl(HttpServletResponse res, Node node) {
if (!node.getCloud().getUser().getRank().equals(org.mmbase.security.Rank.ANONYMOUS.toString())) {
res.setHeader("Cache-Control", "private");
// res.setHeader("Pragma", "no-cache"); // for http 1.0 : is frustrating IE when https
// res.setHeader("Pragma", "no-store"); // no-cache not working in apache!
// we really don't want this to remain in proxy caches, but the http 1.0 way is making IE not work.
return true;
} else {
res.setHeader("Cache-Control", "public");
return false;
}
}
/**
* Serves a node with a byte[] handle field as an attachment.
*/
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
QueryParts query = readQuery(req, res);
Node node = getServedNode(query, getNode(query));
if (node == null) {
log.debug("No node found, returning");
return;
}
if (!node.getNodeManager().hasField("handle")) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, "No handle found in node " + node.getNumber());
return;
}
// fill the headers
res.setDateHeader("Date", System.currentTimeMillis());
String mimeType = getMimeType(node);
res.setContentType(mimeType);
byte[] bytes = node.getByteValue("handle");
if (bytes == null) {
return;
}
/*
* remove additional information left by PhotoShop 7 in jpegs
* , this information may crash Internet Exploder. that's why you need to remove it.
* With PS 7, Adobe decided by default to embed XML-encoded "preview" data into JPEG files,
* using a feature of the JPEG format that permits embedding of arbitrarily-named "profiles".
* In theory, these files are valid according to the JPEG specifications.
* However they break many applications, including Quark and, significantly,
* various versions of Internet Explorer on various platforms.
*/
if (mimeType.equals("image/jpeg") || mimeType.equals("image/jpg")) {
bytes = IECompatibleJpegInputStream.process(bytes);
// res.setHeader("X-MMBase-2", "This image was filtered, because Microsoft Internet Explorer might crash otherwise");
}
if (!setContent(query, node, mimeType)) {
return;
}
setExpires(res, node);
setCacheControl(res, node);
sendBytes(res, bytes);
}
/**
* Utility function to send bytes at the end of doGet implementation.
*/
final protected void sendBytes(HttpServletResponse res, byte[] bytes) throws IOException {
int fileSize = bytes.length;
res.setContentLength(fileSize);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(res.getOutputStream());
} catch (java.io.IOException e) {
log.error(Logging.stackTrace(e));
}
out.write(bytes, 0, fileSize);
out.flush();
out.close();
}
}
/*
This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.
The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license
*/
package org.mmbase.servlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletException;
import java.util.Map;
import org.mmbase.bridge.*;
import org.mmbase.security.Rank;
import org.mmbase.module.builders.Images;
import org.mmbase.util.logging.*;
import org.mmbase.util.functions.*;
/**
* ImageServlet handles nodes as images. If you want to convert an image (resize it, turn it, change
* its colors etc) then you want to serve an 'icaches' node ('icaches' are cached conversions of
* images), which you have to create yourself before calling this servlet. The cache() function of
* Images can be used for this. An URL can be gotten with cachepath().
*
* @version $Id: ImageServlet.java,v 1.15 2003/11/12 13:23:30 michiel Exp $
* @author Michiel Meeuwissen
* @since MMBase-1.6
* @see org.mmbase.module.builders.AbstractImages
* @see org.mmbase.module.builders.Images#executeFunction
* @see AttachmentServlet
*/
public class ImageServlet extends HandleServlet {
private static Logger log;
private boolean convert = false;
public void init() throws ServletException {
super.init();
String convertParameter = getInitParameter("convert");
convert = "true".equals(convertParameter);
log = Logging.getLoggerInstance(ImageServlet.class);
if (convert) {
log.service("Image servlet will accept image conversion templates");
}
}
// just to get ImageServlet in the stacktrace.
protected final Cloud getClassCloud() {
return super.getClassCloud();
}
public String getServletInfo() {
return "Serves (cached) MMBase images";
}
protected Map getAssociations() {
Map a = super.getAssociations();
a.put("images", new Integer(50)); // Is good in images (knows icaches)
a.put("attachments", new Integer(5)); // Can do attachments a little
a.put("downloads", new Integer(-10)); // Can do downloads even worse.
return a;
}
protected String getMimeType(Node node) {
return node.getFunctionValue("mimetype", null).toString();
}
/**
* Content-Disposition header
* [EMAIL PROTECTED]
*/
protected boolean setContent(QueryParts query, Node node, String mimeType) throws java.io.IOException {
String fileName; // will be based on the 'title' field, because images lack a special field for this now.
if (node.getNodeManager().getName().equals("icaches")) {
int originalNode = node.getIntValue("id");
Cloud c = node.getCloud();
if (! c.mayRead(originalNode) && c.getUser().getRank().equals(Rank.ANONYMOUS.toString())) {
// try (again?) cloud from session
c = getCloud(query);
}
if (c == null || ! c.mayRead(originalNode)) {
query.getResponse().sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied on original image node '" + originalNode + "'");
return false;
}
fileName = c.getNode(originalNode).getStringValue("title");
} else { // 'images', but as you see this is not explicit, so you can also name your image builder otherwise.
fileName = node.getStringValue("title");
}
// still not found a sensible fileName? Give it up then.
if (fileName == null || fileName.equals("")) fileName = "mmbase-image";
query.getResponse().setHeader("Content-Disposition", "inline; filename=\"" + fileName + "." + node.getFunctionValue("format", null).toString() + "\"");
return true;
}
/**
* ImageServlet can serve a icache node in stead (using the 'extra parameters'
*
* @since MMBase-1.8
*/
protected Node getServedNode(QueryParts query, Node node) throws java.io.IOException {
Node n = query.getServedNode();
if (n != null) {
return n;
}
String nodeNumber = query.getNodeNumber();
String nodeIdentifier = query.getNodeIdentifier();
if (node.getNodeManager().getName().equals("icaches")) {
if (! nodeNumber.equals(nodeIdentifier)) {
query.getResponse().sendError(HttpServletResponse.SC_FORBIDDEN, "Cannot convert icache node");
return null;
} else {
n = getNode(query);
}
} else {
// This _is_ an original node.
if (! nodeNumber.equals(nodeIdentifier)) {
if (convert) {
Parameters args = new Parameters(Images.CACHE_PARAMETERS);
args.set("template", nodeIdentifier.substring(nodeNumber.length() + 1));
int icacheNodeNumber = node.getFunctionValue("cache", args).toInt();
Cloud cloud = node.getCloud();
cloud = findCloud(cloud, "" + icacheNodeNumber, query);
if (cloud == null) {
return null;
}
Node icache = cloud.getNode(icacheNodeNumber);
n = icache;
} else {
query.getResponse().sendError(HttpServletResponse.SC_FORBIDDEN, "This server does not allow you to convert an image in this way");
return null;
}
} else {
n = getNode(query);
}
}
query.setServedNode(n);
return n;
}
}
/*
This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.
The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license
*/
package org.mmbase.servlet;
import java.io.IOException;
import java.util.regex.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.mmbase.bridge.*;
import org.mmbase.util.logging.*;
/**
* BridgeServlet is an MMBaseServlet with a bridge Cloud in it. Extending from this makes it easy to
* implement servlet implemented with the MMBase bridge interfaces.
*
* An advantage of this is that security is used, which means that you cannot unintentionly serve
* content to the whole world which should actually be protected by the security mechanism.
*
* Another advantage is that implementation using the bridge is easier/clearer.
*
* The query of a bridge servlet can possible start with session=<session-variable-name> in which case the
* cloud is taken from that session attribute with that name. Otherewise 'cloud_mmbase' is
* supposed. All this is only done if there was a session active at all. If not, or the session
* variable was not found, that an anonymous cloud is used.
*
* @version $Id: BridgeServlet.java,v 1.16 2004/02/11 20:43:23 keesj Exp $
* @author Michiel Meeuwissen
* @since MMBase-1.6
*/
public abstract class BridgeServlet extends MMBaseServlet {
/**
* Pattern used for the 'filename' part of the request. The a node-identifying string may be
* present in it, and it the one capturing group.
* It is a digit optionially followed by +.* (used in ImageServlet for url-triggered icache production)
*/
private static final Pattern FILE_PATTERN = Pattern.compile(".*?\\D((?:session=.*?\\+)?\\d+(?:\\+.+?)?)(?:/.*)?");
// some example captured by this regexp:
// /mmbase/images/session=mmbasesession+1234+s(100)/image.jpg
// /mmbase/images/1234+s(100)/image.jpg
// /mmbase/images/1234/image.jpg
// /mmbase/images/1234
// /mmbase/images?1234 (1234 not captured by regexp, but is in query!)
// may not be digits in servlet mapping itself!
private static Logger log;
/**
* This is constant after init.
*/
private static int contextPathLength = -1;
private String lastModifiedField = null;
/**
* The name of the mmbase cloud which must be used. At the moment this is not supported (every
* mmbase cloud is called 'mmbase').
*/
protected String getCloudName() {
return "mmbase";
}
/**
* Creates a QueryParts object which wraps request and response and the parse result of them.
* @return A QueryParts or <code>null</code> if something went wrong (in that case an error was sent, using the response).
*/
protected QueryParts readQuery(HttpServletRequest req, HttpServletResponse res) throws IOException {
QueryParts qp = (QueryParts) req.getAttribute("org.mmbase.servlet.BridgeServlet$QueryParts");
if (qp != null) {
log.trace("no need parsing query");
if (qp.getResponse() == null && res != null) {
qp.setResponse(res);
}
return qp;
}
log.trace("parsing query");
String q = req.getQueryString();
String query;
if (q == null) {
// also possible to use /attachments/[session=abc+]<number>/filename.pdf
if (contextPathLength == -1) {
contextPathLength = req.getContextPath().length();
}
String reqString = req.getRequestURI().substring(contextPathLength); // substring needed, otherwise there may not be digits in context path.
Matcher m = FILE_PATTERN.matcher(reqString);
if (! m.matches()) {
if (res != null) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Malformed URL: '" + reqString + "' does not match '" + FILE_PATTERN.pattern() + "'.");
}
return null;
}
query = m.group(1);
} else {
// attachment.db?[session=abc+]number
query = q;
}
String sessionName = null; // "cloud_" + getCloudName();
String nodeIdentifier;
if (query.startsWith("session=")) {
// indicated the session name in the query: session=<sessionname>+<nodenumber>
int plus = query.indexOf("+", 8);
if (plus == -1) {
if (res != null) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Malformed URL: No node number found after session.");
}
return null;
}
sessionName = query.substring(8, plus);
nodeIdentifier = query.substring(plus + 1);
} else {
nodeIdentifier = query;
}
qp = new QueryParts(sessionName, nodeIdentifier, req, res);
req.setAttribute("org.mmbase.servlet.BridgeServlet$QueryParts", qp);
return qp;
}
/**
* Obtains a cloud object, using a QueryParts object.
* @return A Cloud or <code>null</code> if unsuccessful (this may not be fatal).
*/
final protected Cloud getCloud(QueryParts qp) throws IOException {
log.debug("getting a cloud");
// trying to get a cloud from the session
Cloud cloud = null;
HttpSession session = qp.getRequest().getSession(false); // false: do not create a session, only use it
if (session != null) { // there is a session
log.debug("from session");
String sessionName = qp.getSessionName();
if (sessionName != null) {
cloud = (Cloud) session.getAttribute(sessionName);
} else { // desperately searching for a cloud, perhaps someone forgot to specify 'session_name' to enforce using the session?
cloud = (Cloud) session.getAttribute("cloud_" + getCloudName());
}
}
return cloud;
}
/**
* Obtains an 'anonymous' cloud.
*/
final protected Cloud getAnonymousCloud() {
try {
return ContextProvider.getDefaultCloudContext().getCloud(getCloudName());
} catch (org.mmbase.security.SecurityException e) {
log.debug("could not generate anonymous cloud");
// give it up
return null;
}
}
/**
* Obtains a cloud using 'class' security. If e.g. you authorize org.mmbase.servlet.ImageServlet
* by class-security for read all rights, it will be used.
* @since MMBase-1.8
*/
protected Cloud getClassCloud() {
try {
return ContextProvider.getDefaultCloudContext().getCloud(getCloudName(), "class", null); // testing Class Security
} catch (org.mmbase.security.SecurityException e) {
log.debug("could not generate class cloud");
// give it up
return null;
}
}
/**
* Tries to find a Cloud which can read the given node.
* @since MMBase-1.8
*/
protected Cloud findCloud(Cloud c, String nodeNumber, QueryParts query) throws IOException {
if (c == null || ! (c.mayRead(nodeNumber))) {
c = getClassCloud();
}
if (c == null || ! (c.mayRead(nodeNumber))) {
c = getCloud(query);
}
if (c == null || ! (c.mayRead(nodeNumber))) { // cannot find any cloud what-so-ever,
HttpServletResponse res = query.getResponse();
if (res != null) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied to anonymous for node '" + nodeNumber + "'");
}
return null;
}
return c;
}
/**
* Servlets would often need a node. This function provides it.
* @param query A QueryParts object, which you must have obtained by [EMAIL PROTECTED] readQuery}
*/
final protected Node getNode(QueryParts query) throws IOException {
try {
if (log.isDebugEnabled()) {
log.debug("query : " + query);
}
if (query == null) {
return null;
} else {
Node n = query.getNode();
if (n != null) {
return n;
}
}
Cloud c = getAnonymousCloud(); // first try anonymously always, because then session has not to be used
String nodeNumber = query.getNodeNumber();
if (c != null && ! c.hasNode(nodeNumber)) {
HttpServletResponse res = query.getResponse();
if (res != null) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, "Node '" + nodeNumber + "' does not exist");
}
return null;
}
c = findCloud(c, nodeNumber, query);
if (c == null) {
return null;
}
Node n = c.getNode(nodeNumber);
query.setNode(n);
return n;
} catch (Exception e) {
HttpServletResponse res = query.getResponse();
if (res != null) {
query.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
}
return null;
}
}
/**
* If the node associated with the resonse is another node then the node associated with the request.\
* (E.g. a icache based on a url with an image node).
* @param qp A QueryParts object, which you must have obtained by [EMAIL PROTECTED] readQuery}
* @param node The node which is specified on the URL (obtained by [EMAIL PROTECTED] getNode}
* @since MMBase-1.8
*/
protected Node getServedNode(QueryParts qp, Node node) throws IOException {
return node;
}
/**
* The idea is that a 'bridge servlet' on default serves 'nodes', and that there could be
* defined a 'last modified' time for nodes. This can't be determined right now, so 'now' is
* returned.
*
* This function is defined in HttpServlet
* [EMAIL PROTECTED]
**/
protected long getLastModified(HttpServletRequest req) {
if (lastModifiedField == null) return -1;
try {
QueryParts query = readQuery(req, null);
Node node = getServedNode(query, getNode(query));
if (node != null) { // && node.getNodeManager().hasField(lastModifiedField)) {
return node.getDateValue(lastModifiedField).getTime();
} else {
return -1;
}
} catch (IOException ieo) {
return -1;
}
}
/**
* Inits lastmodifiedField.
* [EMAIL PROTECTED]
*/
public void init() throws ServletException {
super.init();
lastModifiedField = getInitParameter("lastmodifiedfield");
if ("".equals(lastModifiedField)) lastModifiedField = null;
log = Logging.getLoggerInstance(BridgeServlet.class);
if (lastModifiedField != null) {
log.service("Field '" + lastModifiedField + "' will be used to calculate lastModified");
}
}
/**
* Keeps track of determined information, to avoid redetermining it.
*/
final class QueryParts {
private String sessionName;
private String nodeIdentifier;
private HttpServletRequest req;
private HttpServletResponse res;
private Node node;
private Node servedNode;
QueryParts(String sessionName, String nodeIdentifier, HttpServletRequest req, HttpServletResponse res) throws IOException {
this.req = req;
this.res = res;
this.sessionName = sessionName;
this.nodeIdentifier = nodeIdentifier;
}
void setNode(Node node) {
this.node = node;
}
Node getNode() {
return node;
}
void setServedNode(Node node) {
this.servedNode = node;
}
Node getServedNode() {
return servedNode;
}
String getSessionName() { return sessionName; }
String getNodeNumber() {
int i = nodeIdentifier.indexOf('+');
if (i > 0) {
return nodeIdentifier.substring(0, i);
} else {
return nodeIdentifier;
}
}
HttpServletRequest getRequest() {
return req;
}
HttpServletResponse getResponse() {
return res;
}
void setResponse(HttpServletResponse r) {
res = r;
}
/**
* @since MMBase-1.8
*/
String getNodeIdentifier() { return nodeIdentifier; }
public String toString() {
return sessionName == null ? nodeIdentifier : "session=" + sessionName + "+" + nodeIdentifier;
}
}
/**
* Just to test to damn regexp
*/
public static void main(String[] argv) {
Matcher m = FILE_PATTERN.matcher(argv[0]);
if (! m.matches()) {
System.out.println("Didn't match");
} else {
System.out.println("Found node " + m.group(1));
}
}
}
/*
This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.
The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license
*/
package org.mmbase.servlet;
import java.util.Map;
import org.mmbase.bridge.*;
/**
* Serves attachments. An attachments can be any object, as long as it has a byte[] field named
* 'handle'. Also the fields 'filename', 'mimetype' and 'title' can be taken into consideration by
* this servlet and preferably the node has also those fields.
*
* @version $Id: AttachmentServlet.java,v 1.8 2004/09/30 14:54:56 pierre Exp $
* @author Michiel Meeuwissen
* @since MMBase-1.6
* @see HandleServlet
* @see ImageServlet
*/
public class AttachmentServlet extends HandleServlet {
public String getServletInfo() {
return "Serves MMBase nodes as attachments";
}
protected Map getAssociations() {
Map a = super.getAssociations();
a.put("attachments", new Integer(50)); // Is very good in attachments (determines mime-type
// starting with 'attachments' builder fields),
a.put("images", new Integer(10)); // And also can do images (but is not aware of // icaches)
a.put("downloads", new Integer(0));
return a;
}
// just to get AttachmentServlet in the stacktrace.
protected final Cloud getClassCloud() {
return super.getClassCloud();
}
/**
* Determines the mimetype. Can be overridden.
*/
protected String getMimeType(Node node) {
String mimeType = node.getStringValue("mimetype");
if (mimeType == null || mimeType.equals("")) {
// mime-type missing, try to suppose that this is an image node, which has the mimetype
// as a function.
mimeType = node.getFunctionValue("mimetype", null).toString();
if (mimeType == null || mimeType.equals("")) {
return super.getMimeType(node);
}
}
return mimeType;
}
}